Decompiled source of OneWayFacility v1.5.1

OneWayMainEntrance.dll

Decompiled 3 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using DunGen.Graph;
using GameNetcodeStuff;
using HarmonyLib;
using UnityEngine;
using UnityEngine.AI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("OneWayMainEntrance")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("OneWayMainEntrance")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("71b412c6-dd14-4605-b469-0b04f87b3789")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace OneWayFacility;

[BepInPlugin("com.yourname.onewayfacility", "One Way Facility", "1.5.0")]
public class Plugin : BaseUnityPlugin
{
	public enum RouteSafetyVerdict
	{
		Clear,
		Blocked,
		Unresolved
	}

	public sealed class RouteSafetyResult
	{
		public RouteSafetyVerdict Verdict;

		public string Reason;

		public DoorLock BlockingDoor;

		public Vector3 BlockingPoint;
	}

	private sealed class LockedDoorCacheEntry
	{
		public DoorLock Door;

		public Collider Collider;
	}

	public sealed class FireExitInfo
	{
		public int Id;

		public EntranceTeleport InsideDoor;

		public EntranceTeleport OutsideDoor;

		public float DistanceFromMainEntrance;

		public int Segment;

		public bool IsDeadEnd;

		public bool Removed;

		public FireExitInfo(int id, EntranceTeleport insideDoor, EntranceTeleport outsideDoor)
		{
			Id = id;
			InsideDoor = insideDoor;
			OutsideDoor = outsideDoor;
		}
	}

	public static Plugin Instance;

	public static bool HasEscapedThisRun = false;

	public static bool MapHasFireExits = false;

	public static int BlockedDoorId = -1;

	public static bool IsSealActive = false;

	public static bool IsDoorTeleport = false;

	public static Coroutine ActiveSealCoroutine = null;

	public static int LastUsedOutsideDoor = -1;

	public static int PooledInsideFireExitId = -1;

	public static readonly HashSet<int> DeadEndDoorIds = new HashSet<int>();

	public static int LevelGenerationToken = 0;

	public static Coroutine ActiveLevelSetupCoroutine = null;

	public static ConfigEntry<bool> ForceFireExitsAcrossFacility;

	public static ConfigEntry<int> FireExitDistanceRange;

	public static ConfigEntry<bool> ForceOneFireExit;

	public static ConfigEntry<bool> KeysCanUnlockEntryDoor;

	public static ConfigEntry<float> DoorSealTimer;

	public const string ContentModeEnabled = "Enabled";

	public const string ContentModeDontAffectFireExits = "Don't Affect Fire Exits";

	public const string ContentModeDisabled = "Disabled Mod";

	private static readonly string[] ContentModeOptions = new string[3] { "Enabled", "Don't Affect Fire Exits", "Disabled Mod" };

	private static readonly Dictionary<string, ConfigEntry<string>> InteriorModeConfigs = new Dictionary<string, ConfigEntry<string>>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<string, ConfigEntry<string>> MoonModeConfigs = new Dictionary<string, ConfigEntry<string>>(StringComparer.OrdinalIgnoreCase);

	private static readonly Dictionary<string, ConfigEntry<bool>> InteriorIgnoreDeadendConfigs = new Dictionary<string, ConfigEntry<bool>>(StringComparer.OrdinalIgnoreCase);

	internal static readonly HashSet<string> LoggedContentOverrides = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

	private static List<LockedDoorCacheEntry> CachedLockedDoors = null;

	private static float LockedDoorCacheExpiresAt = -1f;

	private const float LockedDoorCacheInterval = 5f;

	private const int RouteMaxSeamHops = 3;

	private const float RouteHopRadius = 2f;

	private const float RouteHopYTolerance = 1.5f;

	private const int RouteHopSampleAngles = 12;

	private const float RouteSeamSampleStep = 0.1f;

	private const float RouteSeamSampleRadius = 0.25f;

	private const float RouteMaximumSeamGap = 0.4f;

	private const float RoutePartialTargetTolerance = 1.5f;

	private const float RouteMaximumBackwardDrift = 3f;

	private static readonly float[] RouteSampleRadii = new float[3] { 2f, 5f, 15f };

	private const float RouteMaximumSampleYDifference = 4f;

	private void Awake()
	{
		//IL_0023: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Expected O, but got Unknown
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Expected O, but got Unknown
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_008b: Expected O, but got Unknown
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Expected O, but got Unknown
		//IL_00e9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f3: Expected O, but got Unknown
		//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0103: Expected O, but got Unknown
		Instance = this;
		ForceFireExitsAcrossFacility = ((BaseUnityPlugin)this).Config.Bind<bool>("Fire Exit Generation", "Force Fire Exits to Generate Across Facility", false, new ConfigDescription("When enabled, DunGen's normally generated fire exits are distance-filtered after generation. Exits outside the configured distance range are completely removed from the scene. Dead-end fire exits are always preserved.", (AcceptableValueBase)null, Array.Empty<object>()));
		FireExitDistanceRange = ((BaseUnityPlugin)this).Config.Bind<int>("Fire Exit Generation", "Fire Exit Distance Range", 10, new ConfigDescription("Minimum facility distance segment for normal/pathable fire exits. The range runs from the selected segment through segment 10. 1 = segments 1-10, 5 = segments 5-10, 10 = segment 10 only.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 10), Array.Empty<object>()));
		ForceOneFireExit = ((BaseUnityPlugin)this).Config.Bind<bool>("Fire Exit Generation", "Force One Fire Exit", false, new ConfigDescription("When enabled, all normal/pathable outdoor fire exits route into one randomly selected surviving indoor fire exit. All other normal/pathable indoor fire exits are completely removed. Dead-end fire exits are never removed or pooled.", (AcceptableValueBase)null, Array.Empty<object>()));
		KeysCanUnlockEntryDoor = ((BaseUnityPlugin)this).Config.Bind<bool>("Door Behavior", "Keys Can Unlock Entry Door", true, new ConfigDescription("Allows a held Key to override and unlock the specific currently sealed entry door. The Key is consumed when used.", (AcceptableValueBase)null, Array.Empty<object>()));
		DoorSealTimer = ((BaseUnityPlugin)this).Config.Bind<float>("Door Behavior", "Door Seal Timer", 10f, new ConfigDescription("How many seconds the entered door remains in its grace period before the seal activates.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>()));
		Harmony val = new Harmony("com.yourname.onewayfacility");
		val.PatchAll();
		((BaseUnityPlugin)this).Logger.LogInfo((object)"[OneWayFacility] Mod loaded successfully!");
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[OneWayFacility] Config loaded. ForceAcross=" + ForceFireExitsAcrossFacility.Value + ", Range=" + FireExitDistanceRange.Value + ", ForceOne=" + ForceOneFireExit.Value + ", Keys=" + KeysCanUnlockEntryDoor.Value + ", SealTimer=" + DoorSealTimer.Value.ToString("0.##") + "s"));
		((MonoBehaviour)this).StartCoroutine(ScanContentConfigurationsDelayed());
	}

	public void Log(string message)
	{
		((BaseUnityPlugin)this).Logger.LogInfo((object)("[OneWayFacility] " + message));
	}

	private IEnumerator ScanContentConfigurationsDelayed()
	{
		yield return null;
		ScanDynamicContentConfigurations();
		yield return null;
		ScanDynamicContentConfigurations();
		yield return (object)new WaitForSeconds(1f);
		ScanDynamicContentConfigurations();
		yield return (object)new WaitForSeconds(2f);
		ScanDynamicContentConfigurations();
		yield return (object)new WaitForSeconds(2f);
		ScanDynamicContentConfigurations();
	}

	private static void ScanDynamicContentConfigurations()
	{
		try
		{
			DungeonFlow[] array = Resources.FindObjectsOfTypeAll<DungeonFlow>();
			DungeonFlow[] array2 = array;
			foreach (DungeonFlow val in array2)
			{
				if (!((Object)(object)val == (Object)null) && !string.IsNullOrWhiteSpace(((Object)val).name))
				{
					AddInteriorConfiguration(((Object)val).name);
				}
			}
		}
		catch (Exception ex)
		{
			Instance.Log("WARNING: Failed to scan interior configurations. " + ex.GetType().Name + ": " + ex.Message);
		}
		try
		{
			SelectableLevel[] array3 = Resources.FindObjectsOfTypeAll<SelectableLevel>();
			SelectableLevel[] array4 = array3;
			foreach (SelectableLevel val2 in array4)
			{
				if (!((Object)(object)val2 == (Object)null))
				{
					string moonDisplayName = GetMoonDisplayName(val2);
					if (!string.IsNullOrWhiteSpace(moonDisplayName))
					{
						AddMoonConfiguration(moonDisplayName);
					}
				}
			}
			if (!((Object)(object)StartOfRound.Instance != (Object)null) || StartOfRound.Instance.levels == null)
			{
				return;
			}
			SelectableLevel[] levels = StartOfRound.Instance.levels;
			foreach (SelectableLevel val3 in levels)
			{
				if (!((Object)(object)val3 == (Object)null))
				{
					string moonDisplayName2 = GetMoonDisplayName(val3);
					if (!string.IsNullOrWhiteSpace(moonDisplayName2))
					{
						AddMoonConfiguration(moonDisplayName2);
					}
				}
			}
		}
		catch (Exception ex2)
		{
			Instance.Log("WARNING: Failed to scan moon configurations. " + ex2.GetType().Name + ": " + ex2.Message);
		}
	}

	private static string GetMoonDisplayName(SelectableLevel level)
	{
		if ((Object)(object)level == (Object)null)
		{
			return string.Empty;
		}
		if (!string.IsNullOrWhiteSpace(level.PlanetName))
		{
			return level.PlanetName;
		}
		return ((Object)level).name;
	}

	private static void AddInteriorConfiguration(string interiorName)
	{
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Expected O, but got Unknown
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a9: Expected O, but got Unknown
		if (!string.IsNullOrWhiteSpace(interiorName))
		{
			if (!InteriorModeConfigs.ContainsKey(interiorName))
			{
				ConfigEntry<string> value = ((BaseUnityPlugin)Instance).Config.Bind<string>("Interior Configuration", interiorName, "Enabled", new ConfigDescription("Controls One Way Facility for this interior. 'Enabled' uses the global settings. 'Don't Affect Fire Exits' keeps the door seal/key system but ignores Force One Fire Exit and Force Fire Exits to Generate Across Facility. 'Disabled Mod' leaves this interior completely vanilla.", (AcceptableValueBase)(object)new AcceptableValueList<string>(ContentModeOptions), Array.Empty<object>()));
				InteriorModeConfigs[interiorName] = value;
			}
			if (!InteriorIgnoreDeadendConfigs.ContainsKey(interiorName))
			{
				ConfigEntry<bool> value2 = ((BaseUnityPlugin)Instance).Config.Bind<bool>("Interior Ignore Deadends", interiorName + " - Ignore Deadends", false, new ConfigDescription("When enabled, dead-end fire exits in this interior are still allowed to seal after the normal grace period. Dead-end exits remain protected from deletion and pooling.", (AcceptableValueBase)null, Array.Empty<object>()));
				InteriorIgnoreDeadendConfigs[interiorName] = value2;
			}
		}
	}

	private static void AddMoonConfiguration(string moonName)
	{
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Expected O, but got Unknown
		if (!string.IsNullOrWhiteSpace(moonName) && !MoonModeConfigs.ContainsKey(moonName))
		{
			ConfigEntry<string> value = ((BaseUnityPlugin)Instance).Config.Bind<string>("Moon Configuration", moonName, "Enabled", new ConfigDescription("Controls One Way Facility for this moon. 'Enabled' uses the global settings. 'Don't Affect Fire Exits' keeps the door seal/key system but ignores Force One Fire Exit and Force Fire Exits to Generate Across Facility. 'Disabled Mod' leaves this moon completely vanilla.", (AcceptableValueBase)(object)new AcceptableValueList<string>(ContentModeOptions), Array.Empty<object>()));
			MoonModeConfigs[moonName] = value;
		}
	}

	private static string NormalizeContentMode(string value)
	{
		if (string.Equals(value, "Don't Affect Fire Exits", StringComparison.OrdinalIgnoreCase))
		{
			return "Don't Affect Fire Exits";
		}
		if (string.Equals(value, "Disabled Mod", StringComparison.OrdinalIgnoreCase))
		{
			return "Disabled Mod";
		}
		return "Enabled";
	}

	private static void LogContentOverrideOnce(string type, string contentName, string mode)
	{
		if (!(mode == "Enabled"))
		{
			string item = type + ":" + contentName + ":" + mode;
			if (!LoggedContentOverrides.Contains(item))
			{
				LoggedContentOverrides.Add(item);
				Instance.Log(type + " override active: " + contentName + " -> " + mode);
			}
		}
	}

	private static string GetCurrentInteriorName()
	{
		try
		{
			if ((Object)(object)RoundManager.Instance != (Object)null && (Object)(object)RoundManager.Instance.dungeonGenerator != (Object)null && RoundManager.Instance.dungeonGenerator.Generator != null && (Object)(object)RoundManager.Instance.dungeonGenerator.Generator.DungeonFlow != (Object)null)
			{
				return ((Object)RoundManager.Instance.dungeonGenerator.Generator.DungeonFlow).name;
			}
		}
		catch
		{
		}
		return string.Empty;
	}

	private static string GetCurrentMoonName()
	{
		try
		{
			if ((Object)(object)RoundManager.Instance != (Object)null && (Object)(object)RoundManager.Instance.currentLevel != (Object)null)
			{
				return GetMoonDisplayName(RoundManager.Instance.currentLevel);
			}
		}
		catch
		{
		}
		try
		{
			if ((Object)(object)StartOfRound.Instance != (Object)null && (Object)(object)StartOfRound.Instance.currentLevel != (Object)null)
			{
				return GetMoonDisplayName(StartOfRound.Instance.currentLevel);
			}
		}
		catch
		{
		}
		return string.Empty;
	}

	public static string GetCurrentContentMode()
	{
		string currentInteriorName = GetCurrentInteriorName();
		string currentMoonName = GetCurrentMoonName();
		if (!string.IsNullOrWhiteSpace(currentInteriorName) && !InteriorModeConfigs.ContainsKey(currentInteriorName))
		{
			AddInteriorConfiguration(currentInteriorName);
		}
		if (!string.IsNullOrWhiteSpace(currentMoonName) && !MoonModeConfigs.ContainsKey(currentMoonName))
		{
			AddMoonConfiguration(currentMoonName);
		}
		if (!string.IsNullOrWhiteSpace(currentInteriorName) && InteriorModeConfigs.TryGetValue(currentInteriorName, out var value))
		{
			string text = NormalizeContentMode(value.Value);
			if (text == "Disabled Mod")
			{
				LogContentOverrideOnce("Interior", currentInteriorName, text);
				return "Disabled Mod";
			}
		}
		if (!string.IsNullOrWhiteSpace(currentMoonName) && MoonModeConfigs.TryGetValue(currentMoonName, out var value2))
		{
			string text2 = NormalizeContentMode(value2.Value);
			if (text2 == "Disabled Mod")
			{
				LogContentOverrideOnce("Moon", currentMoonName, text2);
				return "Disabled Mod";
			}
		}
		if (!string.IsNullOrWhiteSpace(currentInteriorName) && InteriorModeConfigs.TryGetValue(currentInteriorName, out var value3))
		{
			string text3 = NormalizeContentMode(value3.Value);
			if (text3 == "Don't Affect Fire Exits")
			{
				LogContentOverrideOnce("Interior", currentInteriorName, text3);
				return "Don't Affect Fire Exits";
			}
		}
		if (!string.IsNullOrWhiteSpace(currentMoonName) && MoonModeConfigs.TryGetValue(currentMoonName, out var value4))
		{
			string text4 = NormalizeContentMode(value4.Value);
			if (text4 == "Don't Affect Fire Exits")
			{
				LogContentOverrideOnce("Moon", currentMoonName, text4);
				return "Don't Affect Fire Exits";
			}
		}
		return "Enabled";
	}

	public static bool IsModDisabledForCurrentContent()
	{
		return GetCurrentContentMode() == "Disabled Mod";
	}

	public static bool ShouldModifyFireExitGeneration()
	{
		return GetCurrentContentMode() == "Enabled";
	}

	public static bool IsForceOneActive()
	{
		return ShouldModifyFireExitGeneration() && ForceOneFireExit != null && ForceOneFireExit.Value;
	}

	public static bool IsForceAcrossActive()
	{
		return ShouldModifyFireExitGeneration() && ForceFireExitsAcrossFacility != null && ForceFireExitsAcrossFacility.Value;
	}

	public static bool ShouldIgnoreDeadendsForCurrentInterior()
	{
		string currentInteriorName = GetCurrentInteriorName();
		if (string.IsNullOrWhiteSpace(currentInteriorName))
		{
			return false;
		}
		if (!InteriorIgnoreDeadendConfigs.TryGetValue(currentInteriorName, out var value))
		{
			AddInteriorConfiguration(currentInteriorName);
			if (!InteriorIgnoreDeadendConfigs.TryGetValue(currentInteriorName, out value))
			{
				return false;
			}
		}
		return value.Value;
	}

	public static string GetActiveFireExitRangeText()
	{
		int num = Mathf.Clamp((FireExitDistanceRange != null) ? FireExitDistanceRange.Value : 10, 1, 10);
		if (num == 10)
		{
			return "10";
		}
		return num + "-10";
	}

	public static EntranceTeleport FindMainInsideDoor()
	{
		EntranceTeleport[] array = Object.FindObjectsOfType<EntranceTeleport>();
		EntranceTeleport[] array2 = array;
		foreach (EntranceTeleport val in array2)
		{
			if (!((Object)(object)val == (Object)null) && !val.isEntranceToBuilding && val.entranceId == 0)
			{
				return val;
			}
		}
		return null;
	}

	public static EntranceTeleport FindInsideDoor(int entranceId)
	{
		EntranceTeleport[] array = Object.FindObjectsOfType<EntranceTeleport>();
		EntranceTeleport[] array2 = array;
		foreach (EntranceTeleport val in array2)
		{
			if (!((Object)(object)val == (Object)null) && !val.isEntranceToBuilding && val.entranceId == entranceId)
			{
				return val;
			}
		}
		return null;
	}

	public static EntranceTeleport FindOutsideDoor(int entranceId)
	{
		EntranceTeleport[] array = Object.FindObjectsOfType<EntranceTeleport>();
		EntranceTeleport[] array2 = array;
		foreach (EntranceTeleport val in array2)
		{
			if (!((Object)(object)val == (Object)null) && val.isEntranceToBuilding && val.entranceId == entranceId)
			{
				return val;
			}
		}
		return null;
	}

	public static List<FireExitInfo> BuildFireExitList()
	{
		EntranceTeleport[] array = Object.FindObjectsOfType<EntranceTeleport>();
		Dictionary<int, EntranceTeleport> dictionary = new Dictionary<int, EntranceTeleport>();
		Dictionary<int, EntranceTeleport> dictionary2 = new Dictionary<int, EntranceTeleport>();
		HashSet<int> hashSet = new HashSet<int>();
		EntranceTeleport[] array2 = array;
		foreach (EntranceTeleport val in array2)
		{
			if (!((Object)(object)val == (Object)null) && val.entranceId > 0)
			{
				Dictionary<int, EntranceTeleport> dictionary3 = (val.isEntranceToBuilding ? dictionary2 : dictionary);
				if (dictionary3.ContainsKey(val.entranceId))
				{
					hashSet.Add(val.entranceId);
				}
				else
				{
					dictionary3.Add(val.entranceId, val);
				}
			}
		}
		foreach (int item in hashSet)
		{
			Instance.Log("WARNING: Fire Exit ID " + item + " has duplicate EntranceTeleport objects. Destructive filtering for that ID is skipped.");
		}
		List<FireExitInfo> list = new List<FireExitInfo>();
		foreach (KeyValuePair<int, EntranceTeleport> item2 in dictionary)
		{
			int key = item2.Key;
			if (!hashSet.Contains(key))
			{
				if (!dictionary2.TryGetValue(key, out var value))
				{
					Instance.Log("WARNING: Fire Exit ID " + key + " has no matching outdoor EntranceTeleport. No rerouting will be attempted when Force One is OFF.");
				}
				else
				{
					list.Add(new FireExitInfo(key, item2.Value, value));
				}
			}
		}
		return list;
	}

	public static bool TryGetNavMeshPoint(Vector3 position, out Vector3 navMeshPosition)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_002b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001f: Unknown result type (might be due to invalid IL or missing references)
		NavMeshHit val = default(NavMeshHit);
		if (!NavMesh.SamplePosition(position, ref val, 2f, -1))
		{
			navMeshPosition = Vector3.zero;
			return false;
		}
		navMeshPosition = ((NavMeshHit)(ref val)).position;
		return true;
	}

	public static bool HasCompletePathToMainEntrance(EntranceTeleport fireExit, EntranceTeleport mainInsideDoor)
	{
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Expected O, but got Unknown
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		//IL_0085: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Invalid comparison between Unknown and I4
		if ((Object)(object)fireExit == (Object)null || (Object)(object)mainInsideDoor == (Object)null || (Object)(object)fireExit.entrancePoint == (Object)null || (Object)(object)mainInsideDoor.entrancePoint == (Object)null)
		{
			return false;
		}
		if (!TryGetNavMeshPoint(fireExit.entrancePoint.position, out var navMeshPosition))
		{
			return false;
		}
		if (!TryGetNavMeshPoint(mainInsideDoor.entrancePoint.position, out var navMeshPosition2))
		{
			return false;
		}
		NavMeshPath val = new NavMeshPath();
		return NavMesh.CalculatePath(navMeshPosition, navMeshPosition2, -1, val) && (int)val.status == 0;
	}

	public static bool AreAllSurvivingIndoorFireExitsDeadEnds()
	{
		EntranceTeleport[] array = Object.FindObjectsOfType<EntranceTeleport>();
		HashSet<int> hashSet = new HashSet<int>();
		EntranceTeleport[] array2 = array;
		foreach (EntranceTeleport val in array2)
		{
			if (!((Object)(object)val == (Object)null) && !val.isEntranceToBuilding && val.entranceId > 0)
			{
				hashSet.Add(val.entranceId);
			}
		}
		if (hashSet.Count == 0)
		{
			return false;
		}
		foreach (int item in hashSet)
		{
			if (!DeadEndDoorIds.Contains(item))
			{
				return false;
			}
		}
		return true;
	}

	public static void ResetRouteSafetyCache()
	{
		CachedLockedDoors = null;
		LockedDoorCacheExpiresAt = -1f;
		((BaseUnityPlugin)Instance).Logger.LogDebug((object)"[OneWayFacility] Route safety DoorLock cache reset.");
	}

	private static List<LockedDoorCacheEntry> GetLockedDoorCache()
	{
		if (CachedLockedDoors == null || Time.time >= LockedDoorCacheExpiresAt)
		{
			CachedLockedDoors = new List<LockedDoorCacheEntry>();
			DoorLock[] array = Object.FindObjectsOfType<DoorLock>();
			DoorLock[] array2 = array;
			foreach (DoorLock val in array2)
			{
				if (!((Object)(object)val == (Object)null))
				{
					Collider componentInChildren = ((Component)val).GetComponentInChildren<Collider>();
					if (!((Object)(object)componentInChildren == (Object)null))
					{
						LockedDoorCacheEntry lockedDoorCacheEntry = new LockedDoorCacheEntry();
						lockedDoorCacheEntry.Door = val;
						lockedDoorCacheEntry.Collider = componentInChildren;
						CachedLockedDoors.Add(lockedDoorCacheEntry);
					}
				}
			}
			LockedDoorCacheExpiresAt = Time.time + 5f;
			((BaseUnityPlugin)Instance).Logger.LogDebug((object)("[OneWayFacility] Route safety DoorLock cache rebuilt. Found " + CachedLockedDoors.Count + " DoorLock object(s) with colliders."));
		}
		return CachedLockedDoors;
	}

	private static bool TryGetRouteSafetyNavMeshPoint(Vector3 rawPosition, out Vector3 navMeshPosition)
	{
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_006c: 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)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: 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)
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		float[] routeSampleRadii = RouteSampleRadii;
		NavMeshHit val = default(NavMeshHit);
		foreach (float num in routeSampleRadii)
		{
			if (NavMesh.SamplePosition(rawPosition, ref val, num, -1) && !(Mathf.Abs(((NavMeshHit)(ref val)).position.y - rawPosition.y) > 4f))
			{
				navMeshPosition = ((NavMeshHit)(ref val)).position;
				return true;
			}
		}
		navMeshPosition = Vector3.zero;
		return false;
	}

	private static bool SegmentCrossesLockedDoor(Vector3 a, Vector3 b, out DoorLock blockingDoor, out Vector3 hitPoint)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_0016: 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_0038: 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_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ff: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_0141: Unknown result type (might be due to invalid IL or missing references)
		//IL_0142: Unknown result type (might be due to invalid IL or missing references)
		//IL_0145: Unknown result type (might be due to invalid IL or missing references)
		//IL_014a: Unknown result type (might be due to invalid IL or missing references)
		//IL_014f: Unknown result type (might be due to invalid IL or missing references)
		blockingDoor = null;
		hitPoint = Vector3.zero;
		Vector3 val = b - a;
		float magnitude = ((Vector3)(ref val)).magnitude;
		if (magnitude < 0.001f)
		{
			return false;
		}
		Vector3 val2 = val / magnitude;
		float num = float.MaxValue;
		float num2 = default(float);
		foreach (LockedDoorCacheEntry item in GetLockedDoorCache())
		{
			if (item != null && !((Object)(object)item.Door == (Object)null) && !((Object)(object)item.Collider == (Object)null) && item.Door.isLocked)
			{
				Bounds bounds = item.Collider.bounds;
				((Bounds)(ref bounds)).Expand(new Vector3(0.4f, 1f, 0.4f));
				if (((Bounds)(ref bounds)).Contains(a))
				{
					blockingDoor = item.Door;
					hitPoint = a;
					return true;
				}
				if (((Bounds)(ref bounds)).IntersectRay(new Ray(a, val2), ref num2) && num2 >= 0f && num2 <= magnitude && num2 < num)
				{
					num = num2;
					blockingDoor = item.Door;
					hitPoint = a + val2 * num2;
				}
			}
		}
		return (Object)(object)blockingDoor != (Object)null;
	}

	private static bool PathCrossesLockedDoor(Vector3[] corners, out DoorLock blockingDoor, out Vector3 doorPoint)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: 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_004f: Unknown result type (might be due to invalid IL or missing references)
		blockingDoor = null;
		doorPoint = Vector3.zero;
		if (corners == null || corners.Length < 2)
		{
			return false;
		}
		for (int i = 1; i < corners.Length; i++)
		{
			if (SegmentCrossesLockedDoor(corners[i - 1], corners[i], out var blockingDoor2, out var hitPoint))
			{
				blockingDoor = blockingDoor2;
				doorPoint = hitPoint;
				return true;
			}
		}
		return false;
	}

	private static bool HopCrossesSeamOnly(Vector3 a, Vector3 b)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: 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_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_0050: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = b - a;
		float magnitude = ((Vector3)(ref val)).magnitude;
		if (magnitude < 0.001f)
		{
			return true;
		}
		Vector3 val2 = val / magnitude;
		float num = 0f;
		NavMeshHit val4 = default(NavMeshHit);
		for (float num2 = 0f; num2 <= magnitude; num2 += 0.1f)
		{
			Vector3 val3 = a + val2 * num2;
			if (NavMesh.SamplePosition(val3, ref val4, 0.25f, -1))
			{
				num = 0f;
				continue;
			}
			num += 0.1f;
			if (num > 0.4f)
			{
				return false;
			}
		}
		return true;
	}

	private static bool TryFindRouteSafetyBridgeCandidate(Vector3 breakPoint, Vector3 exitPoint, out Vector3 candidate)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_002d: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0050: 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_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		candidate = Vector3.zero;
		float num = Vector3.Distance(breakPoint, exitPoint);
		bool result = false;
		NavMeshHit val2 = default(NavMeshHit);
		for (int i = 0; i < 12; i++)
		{
			float num2 = (float)i * 30f * ((float)Math.PI / 180f);
			Vector3 val = breakPoint + new Vector3(Mathf.Cos(num2) * 2f, 0f, Mathf.Sin(num2) * 2f);
			if (NavMesh.SamplePosition(val, ref val2, 2f, -1) && !(Mathf.Abs(((NavMeshHit)(ref val2)).position.y - breakPoint.y) > 1.5f))
			{
				float num3 = Vector3.Distance(((NavMeshHit)(ref val2)).position, exitPoint);
				if (!(num3 >= num - 0.05f) && !SegmentCrossesLockedDoor(breakPoint, ((NavMeshHit)(ref val2)).position, out var _, out var _) && HopCrossesSeamOnly(breakPoint, ((NavMeshHit)(ref val2)).position))
				{
					num = num3;
					candidate = ((NavMeshHit)(ref val2)).position;
					result = true;
				}
			}
		}
		return result;
	}

	public static RouteSafetyResult ResolveRouteSafety(EntranceTeleport entryDoor, EntranceTeleport mainInsideDoor)
	{
		//IL_00f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
		//IL_0104: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: Unknown result type (might be due to invalid IL or missing references)
		//IL_010a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0136: Unknown result type (might be due to invalid IL or missing references)
		//IL_0162: Unknown result type (might be due to invalid IL or missing references)
		//IL_0163: Unknown result type (might be due to invalid IL or missing references)
		//IL_0165: Unknown result type (might be due to invalid IL or missing references)
		//IL_0167: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a9: Expected O, but got Unknown
		//IL_01a9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_0387: Unknown result type (might be due to invalid IL or missing references)
		//IL_0389: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_03ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_0203: Unknown result type (might be due to invalid IL or missing references)
		//IL_0205: Unknown result type (might be due to invalid IL or missing references)
		//IL_0223: Unknown result type (might be due to invalid IL or missing references)
		//IL_0229: Invalid comparison between Unknown and I4
		//IL_0261: Unknown result type (might be due to invalid IL or missing references)
		//IL_0266: Unknown result type (might be due to invalid IL or missing references)
		//IL_0268: Unknown result type (might be due to invalid IL or missing references)
		//IL_026a: Unknown result type (might be due to invalid IL or missing references)
		//IL_030b: Unknown result type (might be due to invalid IL or missing references)
		//IL_030d: Unknown result type (might be due to invalid IL or missing references)
		//IL_030f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0311: Unknown result type (might be due to invalid IL or missing references)
		//IL_031a: Unknown result type (might be due to invalid IL or missing references)
		//IL_031c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0289: Unknown result type (might be due to invalid IL or missing references)
		//IL_028b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0352: Unknown result type (might be due to invalid IL or missing references)
		//IL_0354: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c9: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_02af: Unknown result type (might be due to invalid IL or missing references)
		//IL_0381: Unknown result type (might be due to invalid IL or missing references)
		//IL_0383: Unknown result type (might be due to invalid IL or missing references)
		RouteSafetyResult routeSafetyResult = new RouteSafetyResult();
		if ((Object)(object)entryDoor == (Object)null)
		{
			routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
			routeSafetyResult.Reason = "Entry door reference is null.";
			return routeSafetyResult;
		}
		if ((Object)(object)mainInsideDoor == (Object)null)
		{
			routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
			routeSafetyResult.Reason = "Main Entrance reference could not be resolved.";
			return routeSafetyResult;
		}
		if ((Object)(object)entryDoor.entrancePoint == (Object)null)
		{
			routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
			routeSafetyResult.Reason = "Entry door has no entrancePoint.";
			return routeSafetyResult;
		}
		if ((Object)(object)mainInsideDoor.entrancePoint == (Object)null)
		{
			routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
			routeSafetyResult.Reason = "Main Entrance has no entrancePoint.";
			return routeSafetyResult;
		}
		if (entryDoor.entranceId == 0 && AreAllSurvivingIndoorFireExitsDeadEnds())
		{
			routeSafetyResult.Verdict = RouteSafetyVerdict.Blocked;
			routeSafetyResult.Reason = "Every surviving indoor fire exit on this level is classified as a dead-end. The Main Entrance is therefore assumed unable to provide a valid path to any surviving fire exit. Main Entrance sealing is disabled.";
			Instance.Log("Main Entrance route safety blocked: every surviving indoor Fire Exit is classified as a dead-end. The Main Entrance will remain usable.");
			return routeSafetyResult;
		}
		Vector3 position = entryDoor.entrancePoint.position;
		Vector3 position2 = mainInsideDoor.entrancePoint.position;
		if (!TryGetRouteSafetyNavMeshPoint(position, out var navMeshPosition))
		{
			routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
			routeSafetyResult.Reason = "Could not obtain a safe NavMesh sample for the entry portal.";
			return routeSafetyResult;
		}
		if (!TryGetRouteSafetyNavMeshPoint(position2, out var navMeshPosition2))
		{
			routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
			routeSafetyResult.Reason = "Could not obtain a safe NavMesh sample for the Main Entrance.";
			return routeSafetyResult;
		}
		Vector3 val = navMeshPosition;
		if (Vector3.Distance(val, navMeshPosition2) <= 1.5f)
		{
			routeSafetyResult.Verdict = RouteSafetyVerdict.Clear;
			routeSafetyResult.Reason = "Entry and Main Entrance resolve to the same local NavMesh point.";
			return routeSafetyResult;
		}
		for (int i = 0; i < 3; i++)
		{
			NavMeshPath val2 = new NavMeshPath();
			bool flag = NavMesh.CalculatePath(val, navMeshPosition2, -1, val2);
			if (val2.corners != null && val2.corners.Length >= 2)
			{
				if (PathCrossesLockedDoor(val2.corners, out var blockingDoor, out var doorPoint))
				{
					routeSafetyResult.Verdict = RouteSafetyVerdict.Blocked;
					routeSafetyResult.BlockingDoor = blockingDoor;
					routeSafetyResult.BlockingPoint = doorPoint;
					routeSafetyResult.Reason = "Computed route intersects a currently locked DoorLock.";
					return routeSafetyResult;
				}
				if (flag && (int)val2.status == 0)
				{
					routeSafetyResult.Verdict = RouteSafetyVerdict.Clear;
					routeSafetyResult.Reason = "Complete NavMesh route found with no locked DoorLock crossing.";
					return routeSafetyResult;
				}
				Vector3 val3 = val2.corners[val2.corners.Length - 1];
				float num = Vector3.Distance(val3, navMeshPosition2);
				if (num <= 1.5f)
				{
					if (SegmentCrossesLockedDoor(val3, navMeshPosition2, out var blockingDoor2, out var hitPoint))
					{
						routeSafetyResult.Verdict = RouteSafetyVerdict.Blocked;
						routeSafetyResult.BlockingDoor = blockingDoor2;
						routeSafetyResult.BlockingPoint = hitPoint;
						routeSafetyResult.Reason = "Final segment toward the Main Entrance intersects a locked DoorLock.";
						return routeSafetyResult;
					}
					if (HopCrossesSeamOnly(val3, navMeshPosition2))
					{
						routeSafetyResult.Verdict = RouteSafetyVerdict.Clear;
						routeSafetyResult.Reason = "Partial NavMesh route terminates within the accepted target tolerance.";
						return routeSafetyResult;
					}
					routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
					routeSafetyResult.Reason = "Partial route reached the target area, but the final gap was too large to prove as a NavMesh seam.";
					return routeSafetyResult;
				}
				Vector3 val4 = val3;
				float num2 = Vector3.Distance(val, navMeshPosition2);
				float num3 = Vector3.Distance(val4, navMeshPosition2);
				if (num3 > num2 + 3f)
				{
					routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
					routeSafetyResult.Reason = "Partial NavMesh route moved significantly away from the Main Entrance.";
					return routeSafetyResult;
				}
				if (!TryFindRouteSafetyBridgeCandidate(val4, navMeshPosition2, out var candidate))
				{
					routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
					routeSafetyResult.Reason = "NavMesh path became partial and no bounded seam-only recovery candidate was found.";
					return routeSafetyResult;
				}
				val = candidate;
				continue;
			}
			if (SegmentCrossesLockedDoor(val, navMeshPosition2, out var blockingDoor3, out var hitPoint2))
			{
				routeSafetyResult.Verdict = RouteSafetyVerdict.Blocked;
				routeSafetyResult.BlockingDoor = blockingDoor3;
				routeSafetyResult.BlockingPoint = hitPoint2;
				routeSafetyResult.Reason = "No usable NavMesh route was found, and the direct route crosses a locked DoorLock.";
				return routeSafetyResult;
			}
			routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
			routeSafetyResult.Reason = "No usable NavMesh route geometry was available.";
			return routeSafetyResult;
		}
		routeSafetyResult.Verdict = RouteSafetyVerdict.Unresolved;
		routeSafetyResult.Reason = "Route safety exhausted its bounded seam-recovery attempts.";
		return routeSafetyResult;
	}

	public unsafe static void LogRouteSafetyFailure(string phase, EntranceTeleport insideDoor, RouteSafetyResult result)
	{
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_0098: Unknown result type (might be due to invalid IL or missing references)
		if (result == null)
		{
			Instance.Log("WARNING: " + phase + " route safety returned no result. Seal will fail open.");
			return;
		}
		string text = (((Object)(object)insideDoor != (Object)null) ? ("ID " + insideDoor.entranceId) : "unknown");
		string text2 = string.Empty;
		if ((Object)(object)result.BlockingDoor != (Object)null)
		{
			string[] obj = new string[5]
			{
				" Blocking DoorLock='",
				((Object)((Component)result.BlockingDoor).gameObject).name,
				"' at ",
				null,
				null
			};
			Vector3 blockingPoint = result.BlockingPoint;
			obj[3] = ((object)(*(Vector3*)(&blockingPoint))/*cast due to .constrained prefix*/).ToString();
			obj[4] = ".";
			text2 = string.Concat(obj);
		}
		Instance.Log("Route safety " + phase + " FAILED for entry " + text + ". Verdict=" + result.Verdict.ToString() + ". " + result.Reason + text2 + " Seal remains disabled (fail-open).");
	}

	public static bool ShouldBypassRouteSafetyForDoor(EntranceTeleport insideDoor)
	{
		if ((Object)(object)insideDoor == (Object)null)
		{
			return false;
		}
		if (insideDoor.entranceId <= 0)
		{
			return false;
		}
		if (!IsProtectedDeadEnd(insideDoor.entranceId))
		{
			return false;
		}
		return ShouldIgnoreDeadendsForCurrentInterior();
	}

	public static void PrepareNormalFireExit(FireExitInfo exit)
	{
		if (exit == null)
		{
			return;
		}
		if ((Object)(object)exit.OutsideDoor != (Object)null)
		{
			try
			{
				exit.OutsideDoor.FindExitPoint();
			}
			catch (Exception ex)
			{
				Instance.Log("WARNING: Failed to initialize outside Fire Exit ID " + exit.Id + ": " + ex.Message);
			}
		}
		if (!((Object)(object)exit.InsideDoor != (Object)null))
		{
			return;
		}
		try
		{
			exit.InsideDoor.FindExitPoint();
		}
		catch (Exception ex2)
		{
			Instance.Log("WARNING: Failed to initialize inside Fire Exit ID " + exit.Id + ": " + ex2.Message);
		}
	}

	public static void SetCachedTeleportTarget(EntranceTeleport teleport, EntranceTeleport destination)
	{
		if ((Object)(object)teleport == (Object)null || (Object)(object)destination == (Object)null || (Object)(object)destination.entrancePoint == (Object)null)
		{
			return;
		}
		try
		{
			Traverse val = Traverse.Create((object)teleport).Field("exitPoint");
			Traverse val2 = Traverse.Create((object)teleport).Field("gotExitPoint");
			if (val.FieldExists())
			{
				val.SetValue((object)destination.entrancePoint);
			}
			if (val2.FieldExists())
			{
				val2.SetValue((object)true);
			}
		}
		catch (Exception ex)
		{
			Instance.Log("WARNING: Could not override cached teleport target: " + ex.Message);
		}
	}

	public static void DestroyFireExit(FireExitInfo info)
	{
		if (info != null && !info.Removed && !info.IsDeadEnd)
		{
			info.Removed = true;
			GameObject val = (((Object)(object)info.OutsideDoor != (Object)null) ? ((Component)info.OutsideDoor).gameObject : null);
			GameObject val2 = (((Object)(object)info.InsideDoor != (Object)null) ? ((Component)info.InsideDoor).gameObject : null);
			if ((Object)(object)val != (Object)null)
			{
				Object.Destroy((Object)(object)val);
			}
			if ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)val)
			{
				Object.Destroy((Object)(object)val2);
			}
			Instance.Log("Removed Fire Exit ID " + info.Id + " completely from the scene.");
		}
	}

	public static void DestroyIndoorFireExit(FireExitInfo info)
	{
		if (info != null && !((Object)(object)info.InsideDoor == (Object)null) && !info.Removed && !info.IsDeadEnd)
		{
			Instance.Log("Force One Fire Exit: removing extra indoor Fire Exit ID " + info.Id + ".");
			Object.Destroy((Object)(object)((Component)info.InsideDoor).gameObject);
		}
	}

	public static List<EntranceTeleport> GetSurvivingPathableIndoorFireExits()
	{
		List<EntranceTeleport> list = new List<EntranceTeleport>();
		EntranceTeleport val = FindMainInsideDoor();
		if ((Object)(object)val == (Object)null)
		{
			return list;
		}
		EntranceTeleport[] array = Object.FindObjectsOfType<EntranceTeleport>();
		EntranceTeleport[] array2 = array;
		foreach (EntranceTeleport val2 in array2)
		{
			if (!((Object)(object)val2 == (Object)null) && !val2.isEntranceToBuilding && val2.entranceId > 0 && !IsProtectedDeadEnd(val2.entranceId) && !((Object)(object)val2.entrancePoint == (Object)null) && HasCompletePathToMainEntrance(val2, val) && !((Object)(object)((Component)val2).GetComponent<InteractTrigger>() == (Object)null))
			{
				list.Add(val2);
			}
		}
		list.Sort((EntranceTeleport a, EntranceTeleport b) => a.entranceId.CompareTo(b.entranceId));
		return list;
	}

	public static void ConfigureFireExitsForCurrentLevel()
	{
		//IL_01af: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_0602: Unknown result type (might be due to invalid IL or missing references)
		//IL_0607: Unknown result type (might be due to invalid IL or missing references)
		//IL_060e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0628: Unknown result type (might be due to invalid IL or missing references)
		//IL_0642: Unknown result type (might be due to invalid IL or missing references)
		DeadEndDoorIds.Clear();
		PooledInsideFireExitId = -1;
		if (IsModDisabledForCurrentContent())
		{
			MapHasFireExits = false;
			return;
		}
		EntranceTeleport val = FindMainInsideDoor();
		if ((Object)(object)val == (Object)null)
		{
			Instance.Log("WARNING: Main interior entrance (ID 0) could not be found. Distance filtering and Force One routing are being skipped.");
			MapHasFireExits = HasAnyFireExitInScene();
			return;
		}
		List<FireExitInfo> list = BuildFireExitList();
		if (list.Count == 0)
		{
			MapHasFireExits = HasAnyFireExitInScene();
			Instance.Log("No complete fire-exit pairs were found.");
			return;
		}
		Instance.Log("Found " + list.Count + " complete fire-exit pair(s).");
		float num = 0f;
		FireExitInfo fireExitInfo = null;
		foreach (FireExitInfo item in list)
		{
			if (!HasCompletePathToMainEntrance(item.InsideDoor, val))
			{
				item.IsDeadEnd = true;
				DeadEndDoorIds.Add(item.Id);
				Instance.Log("Fire Exit ID " + item.Id + " is a DEAD-END. No complete NavMesh path to Main Entrance. It will NOT be removed or pooled.");
				continue;
			}
			if ((Object)(object)item.InsideDoor == (Object)null || (Object)(object)item.InsideDoor.entrancePoint == (Object)null)
			{
				item.IsDeadEnd = true;
				DeadEndDoorIds.Add(item.Id);
				Instance.Log("WARNING: Fire Exit ID " + item.Id + " has no valid inside entrance point. It will be preserved.");
				continue;
			}
			item.DistanceFromMainEntrance = Vector3.Distance(val.entrancePoint.position, item.InsideDoor.entrancePoint.position);
			if (item.DistanceFromMainEntrance > num)
			{
				num = item.DistanceFromMainEntrance;
				fireExitInfo = item;
			}
		}
		if (fireExitInfo == null)
		{
			Instance.Log("No pathable fire exits were found. All dead-end fire exits remain untouched.");
			MapHasFireExits = HasAnyFireExitInScene();
			if (AreAllSurvivingIndoorFireExitsDeadEnds())
			{
				Instance.Log("All surviving indoor fire exits on this level are classified as dead-ends. Main Entrance sealing will be disabled because no surviving fire exit is known to provide an escape path.");
			}
			return;
		}
		Instance.Log("Furthest VALID fire exit is ID " + fireExitInfo.Id + " at " + num.ToString("0.0") + " units.");
		Instance.Log("Active Fire Exit Range: " + GetActiveFireExitRangeText());
		foreach (FireExitInfo item2 in list)
		{
			if (!item2.IsDeadEnd && !item2.Removed)
			{
				if (num <= 0.001f)
				{
					item2.Segment = 10;
					continue;
				}
				float num2 = item2.DistanceFromMainEntrance / num;
				item2.Segment = Mathf.Clamp(Mathf.FloorToInt(num2 * 10f) + 1, 1, 10);
			}
		}
		foreach (FireExitInfo item3 in list)
		{
			if (!item3.IsDeadEnd && !item3.Removed)
			{
				PrepareNormalFireExit(item3);
			}
		}
		if (IsForceAcrossActive())
		{
			int num3 = Mathf.Clamp(FireExitDistanceRange.Value, 1, 10);
			int num4 = 0;
			foreach (FireExitInfo item4 in list)
			{
				if (!item4.IsDeadEnd && !item4.Removed && item4.Segment < num3)
				{
					DestroyFireExit(item4);
					num4++;
				}
			}
			Instance.Log("Distance filtering complete. Removed " + num4 + " normal/pathable fire-exit pair(s).");
		}
		else if (GetCurrentContentMode() == "Don't Affect Fire Exits")
		{
			Instance.Log("Don't Affect Fire Exits is active for this content. Distance filtering was skipped.");
		}
		else
		{
			Instance.Log("Force Fire Exits to Generate Across Facility is OFF. No fire exits were removed because of distance.");
		}
		if (IsForceOneActive())
		{
			List<FireExitInfo> list2 = new List<FireExitInfo>();
			foreach (FireExitInfo item5 in list)
			{
				if (!item5.IsDeadEnd && !item5.Removed && !((Object)(object)item5.InsideDoor == (Object)null) && !((Object)(object)item5.OutsideDoor == (Object)null) && !((Object)(object)((Component)item5.InsideDoor).GetComponent<InteractTrigger>() == (Object)null))
				{
					list2.Add(item5);
				}
			}
			list2.Sort((FireExitInfo a, FireExitInfo b) => a.Id.CompareTo(b.Id));
			if (list2.Count == 0)
			{
				Instance.Log("Force One Fire Exit is enabled, but no valid normal/pathable fire exit survived filtering.");
			}
			else
			{
				int num5 = 17;
				foreach (FireExitInfo item6 in list2)
				{
					num5 = num5 * 31 + item6.Id;
					num5 = num5 * 31 + item6.Segment;
					if ((Object)(object)item6.InsideDoor != (Object)null && (Object)(object)item6.InsideDoor.entrancePoint != (Object)null)
					{
						Vector3 position = item6.InsideDoor.entrancePoint.position;
						num5 = num5 * 31 + Mathf.RoundToInt(position.x * 10f);
						num5 = num5 * 31 + Mathf.RoundToInt(position.y * 10f);
						num5 = num5 * 31 + Mathf.RoundToInt(position.z * 10f);
					}
				}
				if (num5 == int.MinValue)
				{
					num5 = 123456789;
				}
				Random random = new Random(num5);
				int index = random.Next(list2.Count);
				FireExitInfo fireExitInfo2 = list2[index];
				PooledInsideFireExitId = fireExitInfo2.Id;
				Instance.Log("Force One Fire Exit: selected indoor Fire Exit ID " + PooledInsideFireExitId + " as the shared pooled destination.");
				int num6 = 0;
				foreach (FireExitInfo item7 in list2)
				{
					if (item7.Id != PooledInsideFireExitId)
					{
						DestroyIndoorFireExit(item7);
						num6++;
					}
				}
				Instance.Log("Force One Fire Exit: removed " + num6 + " extra indoor fire exit(s).");
				foreach (FireExitInfo item8 in list2)
				{
					if (!((Object)(object)item8.OutsideDoor == (Object)null))
					{
						if (item8.Id == PooledInsideFireExitId)
						{
							PrepareNormalFireExit(item8);
						}
						else
						{
							SetCachedTeleportTarget(item8.OutsideDoor, fireExitInfo2.InsideDoor);
						}
					}
				}
				Instance.Log("Force One routing initialized. All normal/pathable outdoor fire exits use indoor Fire Exit ID " + PooledInsideFireExitId + ".");
			}
		}
		else if (GetCurrentContentMode() == "Don't Affect Fire Exits")
		{
			Instance.Log("Don't Affect Fire Exits is active for this content. Force One Fire Exit was skipped.");
		}
		else
		{
			Instance.Log("Force One Fire Exit is OFF. All normal surviving fire exits retain vanilla routing.");
		}
		if (AreAllSurvivingIndoorFireExitsDeadEnds())
		{
			Instance.Log("Main Entrance safety rule ACTIVE: every surviving indoor fire exit on this level is classified as a dead-end. The Main Entrance will remain usable and will not begin the One Way Facility seal.");
		}
		else
		{
			Instance.Log("Main Entrance safety rule inactive: at least one surviving indoor fire exit is not classified as a dead-end.");
		}
	}

	public static bool HasAnyFireExitInScene()
	{
		EntranceTeleport[] array = Object.FindObjectsOfType<EntranceTeleport>();
		EntranceTeleport[] array2 = array;
		foreach (EntranceTeleport val in array2)
		{
			if ((Object)(object)val != (Object)null && val.entranceId > 0)
			{
				return true;
			}
		}
		return false;
	}

	public static int CountFireExitsInScene()
	{
		int num = 0;
		HashSet<int> hashSet = new HashSet<int>();
		EntranceTeleport[] array = Object.FindObjectsOfType<EntranceTeleport>();
		EntranceTeleport[] array2 = array;
		foreach (EntranceTeleport val in array2)
		{
			if (!((Object)(object)val == (Object)null) && val.entranceId > 0 && hashSet.Add(val.entranceId))
			{
				num++;
			}
		}
		return num;
	}

	public static bool IsProtectedDeadEnd(int entranceId)
	{
		return DeadEndDoorIds.Contains(entranceId);
	}
}
[HarmonyPatch(typeof(RoundManager), "FinishGeneratingNewLevelClientRpc")]
public class LevelLoadPatch
{
	private static void Postfix()
	{
		Plugin.LevelGenerationToken++;
		int levelGenerationToken = Plugin.LevelGenerationToken;
		Plugin.HasEscapedThisRun = false;
		Plugin.LastUsedOutsideDoor = -1;
		Plugin.PooledInsideFireExitId = -1;
		Plugin.DeadEndDoorIds.Clear();
		Plugin.ResetRouteSafetyCache();
		DoorTeleportPatch.CancelSeal();
		if (Plugin.ActiveLevelSetupCoroutine != null)
		{
			((MonoBehaviour)Plugin.Instance).StopCoroutine(Plugin.ActiveLevelSetupCoroutine);
			Plugin.ActiveLevelSetupCoroutine = null;
		}
		Plugin.ActiveLevelSetupCoroutine = ((MonoBehaviour)Plugin.Instance).StartCoroutine(ConfigureAfterDunGenFinishes(levelGenerationToken));
	}

	private static IEnumerator ConfigureAfterDunGenFinishes(int generationToken)
	{
		yield return null;
		yield return null;
		if (generationToken != Plugin.LevelGenerationToken)
		{
			yield break;
		}
		if (Plugin.IsModDisabledForCurrentContent())
		{
			Plugin.MapHasFireExits = false;
			Plugin.Instance.Log("One Way Facility is disabled for the current content. Leaving the level vanilla.");
			Plugin.ActiveLevelSetupCoroutine = null;
			yield break;
		}
		Plugin.Instance.Log("DunGen generation complete. Beginning fire-exit analysis...");
		Plugin.ConfigureFireExitsForCurrentLevel();
		if (generationToken != Plugin.LevelGenerationToken)
		{
			yield break;
		}
		yield return null;
		if (generationToken == Plugin.LevelGenerationToken)
		{
			Plugin.MapHasFireExits = Plugin.HasAnyFireExitInScene();
			int remaining = Plugin.CountFireExitsInScene();
			Plugin.Instance.Log("Fire-exit setup complete. Remaining fire-exit IDs: " + remaining + ". Map Has Fire Exits: " + Plugin.MapHasFireExits + ".");
			if (Plugin.MapHasFireExits && Plugin.AreAllSurvivingIndoorFireExitsDeadEnds())
			{
				Plugin.Instance.Log("FINAL LEVEL SAFETY STATE: all surviving indoor fire exits are classified as dead-ends. Main Entrance sealing is disabled for this level.");
			}
			Plugin.ActiveLevelSetupCoroutine = null;
		}
	}
}
[HarmonyPatch(typeof(EntranceTeleport), "TeleportPlayer")]
public class DoorTeleportPatch
{
	private sealed class TeleportState
	{
		public int OriginalEntranceId = -1;

		public bool WasRemapped = false;

		public int SealDoorId = -1;

		public Transform OriginalExitPoint = null;

		public bool OriginalGotExitPoint = false;

		public bool HasOriginalExitPointState = false;
	}

	private static void SaveTeleportDestination(EntranceTeleport teleport, TeleportState state)
	{
		if ((Object)(object)teleport == (Object)null || state == null)
		{
			return;
		}
		try
		{
			Traverse val = Traverse.Create((object)teleport).Field("exitPoint");
			Traverse val2 = Traverse.Create((object)teleport).Field("gotExitPoint");
			if (val.FieldExists())
			{
				state.OriginalExitPoint = val.GetValue<Transform>();
				state.HasOriginalExitPointState = true;
			}
			if (val2.FieldExists())
			{
				state.OriginalGotExitPoint = val2.GetValue<bool>();
				state.HasOriginalExitPointState = true;
			}
		}
		catch
		{
			state.HasOriginalExitPointState = false;
		}
	}

	private static void RestoreTeleportDestination(EntranceTeleport teleport, TeleportState state)
	{
		if ((Object)(object)teleport == (Object)null || state == null || !state.HasOriginalExitPointState)
		{
			return;
		}
		try
		{
			Traverse val = Traverse.Create((object)teleport).Field("exitPoint");
			Traverse val2 = Traverse.Create((object)teleport).Field("gotExitPoint");
			if (val.FieldExists())
			{
				val.SetValue((object)state.OriginalExitPoint);
			}
			if (val2.FieldExists())
			{
				val2.SetValue((object)state.OriginalGotExitPoint);
			}
		}
		catch
		{
		}
	}

	private static bool Prefix(EntranceTeleport __instance, ref TeleportState __state)
	{
		__state = new TeleportState();
		Plugin.IsDoorTeleport = true;
		if ((Object)(object)__instance == (Object)null)
		{
			return true;
		}
		__state.OriginalEntranceId = __instance.entranceId;
		SaveTeleportDestination(__instance, __state);
		if (Plugin.IsModDisabledForCurrentContent())
		{
			return true;
		}
		if ((Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null)
		{
			return true;
		}
		PlayerControllerB localPlayerController = GameNetworkManager.Instance.localPlayerController;
		if (!__instance.isEntranceToBuilding && Plugin.IsSealActive && Plugin.BlockedDoorId == __instance.entranceId)
		{
			bool flag = false;
			if ((Object)(object)localPlayerController.currentlyHeldObjectServer != (Object)null && (Object)(object)localPlayerController.currentlyHeldObjectServer.itemProperties != (Object)null)
			{
				flag = localPlayerController.currentlyHeldObjectServer.itemProperties.itemName == "Key";
			}
			if (!(Plugin.KeysCanUnlockEntryDoor.Value && flag))
			{
				Plugin.IsDoorTeleport = false;
				return false;
			}
			Plugin.Instance.Log("Key used! Consuming key and overriding seal.");
			localPlayerController.DespawnHeldObject();
		}
		if (__instance.isEntranceToBuilding)
		{
			if (__instance.entranceId == 0)
			{
				Plugin.LastUsedOutsideDoor = -1;
				return true;
			}
			if (__instance.entranceId > 0)
			{
				int entranceId = __instance.entranceId;
				if (Plugin.IsForceOneActive())
				{
					if (Plugin.IsProtectedDeadEnd(entranceId))
					{
						Plugin.LastUsedOutsideDoor = entranceId;
						return true;
					}
					Plugin.LastUsedOutsideDoor = entranceId;
					int pooledInsideFireExitId = Plugin.PooledInsideFireExitId;
					if (pooledInsideFireExitId <= 0)
					{
						return true;
					}
					if (pooledInsideFireExitId == entranceId)
					{
						return true;
					}
					EntranceTeleport val = Plugin.FindInsideDoor(pooledInsideFireExitId);
					if ((Object)(object)val == (Object)null || (Object)(object)val.entrancePoint == (Object)null)
					{
						Plugin.Instance.Log("WARNING: Force One destination ID " + pooledInsideFireExitId + " could not be found. Falling back to normal routing.");
						return true;
					}
					__instance.entranceId = pooledInsideFireExitId;
					Plugin.SetCachedTeleportTarget(__instance, val);
					__state.WasRemapped = true;
					__state.SealDoorId = pooledInsideFireExitId;
					Plugin.Instance.Log("Force One routing: outside Fire Exit ID " + entranceId + " -> pooled indoor Fire Exit ID " + pooledInsideFireExitId + ".");
					return true;
				}
				return true;
			}
		}
		if (!__instance.isEntranceToBuilding && Plugin.IsForceOneActive() && Plugin.PooledInsideFireExitId > 0 && __instance.entranceId == Plugin.PooledInsideFireExitId)
		{
			int lastUsedOutsideDoor = Plugin.LastUsedOutsideDoor;
			if (lastUsedOutsideDoor <= 0)
			{
				return true;
			}
			if (Plugin.IsProtectedDeadEnd(lastUsedOutsideDoor))
			{
				return true;
			}
			EntranceTeleport val2 = Plugin.FindOutsideDoor(lastUsedOutsideDoor);
			if ((Object)(object)val2 == (Object)null || (Object)(object)val2.entrancePoint == (Object)null)
			{
				Plugin.Instance.Log("WARNING: Saved outside Fire Exit ID " + lastUsedOutsideDoor + " could not be found. Using normal pooled routing.");
				return true;
			}
			SaveTeleportDestination(__instance, __state);
			__instance.entranceId = lastUsedOutsideDoor;
			Plugin.SetCachedTeleportTarget(__instance, val2);
			__state.WasRemapped = true;
			Plugin.Instance.Log("Force One routing: pooled indoor Fire Exit ID " + Plugin.PooledInsideFireExitId + " -> local player's saved outside Fire Exit ID " + lastUsedOutsideDoor + ".");
			return true;
		}
		return true;
	}

	private static void Postfix(EntranceTeleport __instance, TeleportState __state)
	{
		if (__state != null && __state.WasRemapped && (Object)(object)__instance != (Object)null)
		{
			RestoreTeleportDestination(__instance, __state);
			__instance.entranceId = __state.OriginalEntranceId;
		}
		if (Plugin.IsModDisabledForCurrentContent())
		{
			Plugin.IsDoorTeleport = false;
			return;
		}
		PlayerControllerB val = null;
		if ((Object)(object)GameNetworkManager.Instance != (Object)null)
		{
			val = GameNetworkManager.Instance.localPlayerController;
		}
		if ((Object)(object)val == (Object)null)
		{
			Plugin.IsDoorTeleport = false;
			return;
		}
		if ((Object)(object)__instance != (Object)null && __instance.isEntranceToBuilding)
		{
			if (Plugin.MapHasFireExits && !Plugin.HasEscapedThisRun)
			{
				int num = __state?.OriginalEntranceId ?? __instance.entranceId;
				if (num < 0)
				{
					Plugin.IsDoorTeleport = false;
					return;
				}
				if (num > 0 && Plugin.IsProtectedDeadEnd(num) && !Plugin.ShouldIgnoreDeadendsForCurrentInterior())
				{
					Plugin.Instance.Log("Fire Exit ID " + num + " is a protected dead-end. Door remains unlocked.");
					Plugin.IsDoorTeleport = false;
					return;
				}
				int entranceId = num;
				if (__state != null && __state.SealDoorId > 0)
				{
					entranceId = __state.SealDoorId;
				}
				EntranceTeleport val2 = Plugin.FindInsideDoor(entranceId);
				if ((Object)(object)val2 != (Object)null)
				{
					CancelSeal();
					if (Plugin.ShouldBypassRouteSafetyForDoor(val2))
					{
						Plugin.Instance.Log("Route safety bypassed for Fire Exit ID " + val2.entranceId + " because Ignore Deadends is enabled for the current interior. Existing dead-end seal behavior is preserved.");
					}
					else
					{
						EntranceTeleport mainInsideDoor = Plugin.FindMainInsideDoor();
						Plugin.Instance.Log("Route safety entry check starting for sealed-door target ID " + val2.entranceId + ".");
						Plugin.RouteSafetyResult routeSafetyResult = Plugin.ResolveRouteSafety(val2, mainInsideDoor);
						if (routeSafetyResult == null || routeSafetyResult.Verdict != Plugin.RouteSafetyVerdict.Clear)
						{
							Plugin.LogRouteSafetyFailure("entry", val2, routeSafetyResult);
							Plugin.IsDoorTeleport = false;
							return;
						}
						Plugin.Instance.Log("Route safety entry check PASSED for sealed-door target ID " + val2.entranceId + ". A clear route to the Main Entrance was confirmed.");
					}
					Plugin.BlockedDoorId = val2.entranceId;
					Plugin.ActiveSealCoroutine = ((MonoBehaviour)Plugin.Instance).StartCoroutine(DoorLockRoutine(val2));
				}
				else
				{
					Plugin.Instance.Log("WARNING: Could not find interior door for Fire Exit ID " + entranceId + ". No seal started.");
				}
			}
		}
		else if ((Object)(object)__instance != (Object)null)
		{
			if (!Plugin.HasEscapedThisRun)
			{
				if (Plugin.IsSealActive)
				{
					Plugin.HasEscapedThisRun = true;
					Plugin.Instance.Log("First successful escape completed! All doors permanently unlocked for this run.");
					CancelSeal();
				}
				else
				{
					Plugin.Instance.Log("Exited during grace period. Resetting seal to square one.");
					CancelSeal();
				}
			}
			Plugin.LastUsedOutsideDoor = -1;
		}
		Plugin.IsDoorTeleport = false;
	}

	public static IEnumerator DoorLockRoutine(EntranceTeleport insideDoor)
	{
		Plugin.IsSealActive = false;
		if ((Object)(object)insideDoor == (Object)null)
		{
			Plugin.BlockedDoorId = -1;
			Plugin.ActiveSealCoroutine = null;
			yield break;
		}
		InteractTrigger trigger = ((Component)insideDoor).GetComponent<InteractTrigger>();
		if ((Object)(object)trigger == (Object)null)
		{
			Plugin.Instance.Log("WARNING: Fire Exit ID " + insideDoor.entranceId + " has no InteractTrigger. Seal cancelled.");
			Plugin.BlockedDoorId = -1;
			Plugin.ActiveSealCoroutine = null;
			yield break;
		}
		float remaining = Mathf.Max(0f, Plugin.DoorSealTimer.Value);
		while (remaining > 0f)
		{
			if (Plugin.BlockedDoorId != insideDoor.entranceId || Plugin.IsModDisabledForCurrentContent())
			{
				yield break;
			}
			if ((Object)(object)trigger == (Object)null)
			{
				Plugin.IsSealActive = false;
				Plugin.BlockedDoorId = -1;
				Plugin.ActiveSealCoroutine = null;
				yield break;
			}
			trigger.interactable = true;
			trigger.hoverTip = "Sealing in " + Mathf.CeilToInt(remaining) + "s...";
			float waitTime = Mathf.Min(1f, remaining);
			yield return (object)new WaitForSeconds(waitTime);
			remaining -= waitTime;
		}
		if (Plugin.ShouldBypassRouteSafetyForDoor(insideDoor))
		{
			Plugin.Instance.Log("Final route safety re-check bypassed for Fire Exit ID " + insideDoor.entranceId + " because Ignore Deadends is enabled.");
		}
		else
		{
			Plugin.Instance.Log("Grace period ended. Performing final route safety re-check before sealing Door ID " + insideDoor.entranceId + ".");
			EntranceTeleport mainInsideDoor = Plugin.FindMainInsideDoor();
			Plugin.RouteSafetyResult finalSafetyResult = Plugin.ResolveRouteSafety(insideDoor, mainInsideDoor);
			if (finalSafetyResult == null || finalSafetyResult.Verdict != Plugin.RouteSafetyVerdict.Clear)
			{
				Plugin.LogRouteSafetyFailure("final", insideDoor, finalSafetyResult);
				Plugin.IsSealActive = false;
				Plugin.BlockedDoorId = -1;
				Plugin.ActiveSealCoroutine = null;
				trigger.interactable = true;
				trigger.hoverTip = "Exit : [LMB]";
				trigger.disabledHoverTip = "";
				Plugin.IsDoorTeleport = false;
				yield break;
			}
			Plugin.Instance.Log("Final route safety re-check PASSED for Door ID " + insideDoor.entranceId + ".");
		}
		Plugin.IsSealActive = true;
		Plugin.Instance.Log("Grace period ended and route safety was confirmed. Door ID " + insideDoor.entranceId + " is now sealed.");
		PlayerControllerB localPlayer = null;
		if ((Object)(object)GameNetworkManager.Instance != (Object)null)
		{
			localPlayer = GameNetworkManager.Instance.localPlayerController;
		}
		while (Plugin.IsSealActive && Plugin.BlockedDoorId == insideDoor.entranceId && !Plugin.IsModDisabledForCurrentContent())
		{
			if ((Object)(object)trigger == (Object)null)
			{
				Plugin.IsSealActive = false;
				Plugin.BlockedDoorId = -1;
				Plugin.ActiveSealCoroutine = null;
				break;
			}
			bool holdingKey = false;
			if ((Object)(object)localPlayer != (Object)null && (Object)(object)localPlayer.currentlyHeldObjectServer != (Object)null && (Object)(object)localPlayer.currentlyHeldObjectServer.itemProperties != (Object)null)
			{
				holdingKey = localPlayer.currentlyHeldObjectServer.itemProperties.itemName == "Key";
			}
			if (Plugin.KeysCanUnlockEntryDoor.Value && holdingKey)
			{
				trigger.interactable = true;
				trigger.hoverTip = "[Key] Override Seal : [LMB]";
				trigger.disabledHoverTip = "";
			}
			else
			{
				trigger.interactable = false;
				trigger.disabledHoverTip = "Entrance sealed, find another exit!";
			}
			yield return (object)new WaitForSeconds(0.15f);
		}
	}

	public static void CancelSeal()
	{
		if (Plugin.ActiveSealCoroutine != null)
		{
			((MonoBehaviour)Plugin.Instance).StopCoroutine(Plugin.ActiveSealCoroutine);
			Plugin.ActiveSealCoroutine = null;
		}
		Plugin.BlockedDoorId = -1;
		Plugin.IsSealActive = false;
		EntranceTeleport[] array = Object.FindObjectsOfType<EntranceTeleport>();
		EntranceTeleport[] array2 = array;
		foreach (EntranceTeleport val in array2)
		{
			if (!((Object)(object)val == (Object)null) && !val.isEntranceToBuilding)
			{
				InteractTrigger component = ((Component)val).GetComponent<InteractTrigger>();
				if (!((Object)(object)component == (Object)null))
				{
					component.interactable = true;
					component.hoverTip = "Exit : [LMB]";
					component.disabledHoverTip = "";
				}
			}
		}
		Plugin.IsDoorTeleport = false;
	}
}
[HarmonyPatch(typeof(PlayerControllerB), "TeleportPlayer")]
public class PlayerTeleportPatch
{
	private static void Postfix(PlayerControllerB __instance)
	{
		if (Plugin.IsDoorTeleport)
		{
			return;
		}
		if (Plugin.IsModDisabledForCurrentContent())
		{
			Plugin.IsDoorTeleport = false;
		}
		else
		{
			if ((Object)(object)GameNetworkManager.Instance == (Object)null || (Object)(object)GameNetworkManager.Instance.localPlayerController == (Object)null)
			{
				return;
			}
			if ((Object)(object)__instance == (Object)(object)GameNetworkManager.Instance.localPlayerController)
			{
				Plugin.LastUsedOutsideDoor = -1;
				if (!Plugin.HasEscapedThisRun)
				{
					Plugin.Instance.Log("Player was teleported by Ship/Inverse or another non-door teleport. Resetting door states to square one.");
					DoorTeleportPatch.CancelSeal();
				}
			}
			Plugin.IsDoorTeleport = false;
		}
	}
}