Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of OneWayFacility v1.5.1
OneWayMainEntrance.dll
Decompiled 3 days agousing 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; } } }