Decompiled source of GuaranteeApparatus v1.0.1

BepInEx/plugins/GuaranteeApparatus/GuaranteeApparatus.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using DunGen;
using DunGen.Graph;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace GuaranteeApparatus
{
	internal enum InteriorClassification
	{
		Vanilla,
		KnownCustom,
		UnknownCustom
	}
	internal sealed class ValidationContext
	{
		[CompilerGenerated]
		private sealed class <FindSceneObjects>d__39<T> : IEnumerable<T>, IEnumerable, IEnumerator<T>, IEnumerator, IDisposable where T : notnull, Component
		{
			private int <>1__state;

			private T <>2__current;

			private int <>l__initialThreadId;

			private T[] <allObjects>5__2;

			private int <i>5__3;

			T IEnumerator<T>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <FindSceneObjects>d__39(int <>1__state)
			{
				this.<>1__state = <>1__state;
				<>l__initialThreadId = Environment.CurrentManagedThreadId;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<allObjects>5__2 = null;
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0056: Unknown result type (might be due to invalid IL or missing references)
				//IL_005b: Unknown result type (might be due to invalid IL or missing references)
				int num = <>1__state;
				if (num != 0)
				{
					if (num != 1)
					{
						return false;
					}
					<>1__state = -1;
					goto IL_007c;
				}
				<>1__state = -1;
				<allObjects>5__2 = Resources.FindObjectsOfTypeAll<T>();
				<i>5__3 = 0;
				goto IL_008c;
				IL_007c:
				<i>5__3++;
				goto IL_008c;
				IL_008c:
				if (<i>5__3 < <allObjects>5__2.Length)
				{
					T val = <allObjects>5__2[<i>5__3];
					if (!((Object)(object)val == (Object)null))
					{
						Scene scene = ((Component)val).gameObject.scene;
						if (((Scene)(ref scene)).IsValid())
						{
							<>2__current = val;
							<>1__state = 1;
							return true;
						}
					}
					goto IL_007c;
				}
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}

			[DebuggerHidden]
			IEnumerator<T> IEnumerable<T>.GetEnumerator()
			{
				if (<>1__state == -2 && <>l__initialThreadId == Environment.CurrentManagedThreadId)
				{
					<>1__state = 0;
					return this;
				}
				return new <FindSceneObjects>d__39<T>(0);
			}

			[DebuggerHidden]
			IEnumerator IEnumerable.GetEnumerator()
			{
				return ((IEnumerable<T>)this).GetEnumerator();
			}
		}

		public RoundManager RoundManager { get; }

		public StartOfRound StartOfRound { get; }

		public Dungeon Dungeon { get; }

		public List<Tile> Tiles { get; }

		public Bounds DungeonBounds { get; }

		public LllContext LllContext { get; }

		public string FlowName { get; }

		public bool IsFactoryLike { get; }

		public InteriorClassification Classification { get; set; }

		public IKnownCustomInteriorRule? KnownRule { get; set; }

		public Vector3 EntrancePosition { get; set; }

		public Transform DungeonRootTransform => ((Component)Dungeon).transform;

		public ValidationContext(RoundManager roundManager, StartOfRound startOfRound, Dungeon dungeon, List<Tile> tiles, LllContext lllContext, string flowName, bool isFactoryLike)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			RoundManager = roundManager;
			StartOfRound = startOfRound;
			Dungeon = dungeon;
			Tiles = tiles;
			LllContext = lllContext;
			FlowName = flowName;
			IsFactoryLike = isFactoryLike;
			DungeonBounds = BuildDungeonBounds(tiles);
		}

		[IteratorStateMachine(typeof(<FindSceneObjects>d__39<>))]
		public IEnumerable<T> FindSceneObjects<T>() where T : Component
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <FindSceneObjects>d__39<T>(-2);
		}

		public T[] FindDungeonComponents<T>() where T : Component
		{
			return ((Component)DungeonRootTransform).GetComponentsInChildren<T>(true);
		}

		public Transform[] FindDungeonTransforms()
		{
			return ((Component)DungeonRootTransform).GetComponentsInChildren<Transform>(true);
		}

		public bool IsInDungeonHierarchy(Transform? transform)
		{
			if ((Object)(object)transform != (Object)null)
			{
				if (!((Object)(object)transform == (Object)(object)DungeonRootTransform))
				{
					return transform.IsChildOf(DungeonRootTransform);
				}
				return true;
			}
			return false;
		}

		public bool IsInDungeon(Vector3 position)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			Tile tile;
			Bounds containingBounds;
			return TryFindContainingTile(position, out tile, out containingBounds);
		}

		public bool TryFindContainingTile(Vector3 position, out Tile? tile, out Bounds containingBounds)
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			for (int i = 0; i < Tiles.Count; i++)
			{
				Tile val = Tiles[i];
				Bounds val2 = ExpandedBounds(GetTileBounds(val), 1.5f);
				if (((Bounds)(ref val2)).Contains(position))
				{
					tile = val;
					containingBounds = val2;
					return true;
				}
			}
			tile = null;
			containingBounds = default(Bounds);
			return false;
		}

		public Bounds GetTileBounds(Tile tile)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			return tile.Bounds;
		}

		public string GetRelativeDungeonPath(Transform transform)
		{
			return ApparatusValidator.GetRelativeTransformPath(transform, DungeonRootTransform);
		}

		public static Bounds ExpandedBounds(Bounds bounds, float padding)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: 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)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			return new Bounds(((Bounds)(ref bounds)).center, ((Bounds)(ref bounds)).size + Vector3.one * (padding * 2f));
		}

		private static Bounds BuildDungeonBounds(List<Tile> tiles)
		{
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: 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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: 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)
			if (tiles.Count == 0)
			{
				return new Bounds(Vector3.zero, Vector3.zero);
			}
			Bounds bounds = tiles[0].Bounds;
			for (int i = 1; i < tiles.Count; i++)
			{
				((Bounds)(ref bounds)).Encapsulate(tiles[i].Bounds);
			}
			return bounds;
		}
	}
	internal readonly struct SpawnCandidate
	{
		public Vector3 Position { get; }

		public float DistanceFromEntrance { get; }

		public float TileVolume { get; }

		public float NormalizedDepth { get; }

		public bool PreferredUnusedAnchor { get; }

		public string StableKey { get; }

		public string RelativeDungeonPath { get; }

		public SpawnCandidate(Vector3 position, float distanceFromEntrance, float tileVolume, float normalizedDepth, bool preferredUnusedAnchor, string stableKey, string relativeDungeonPath)
		{
			//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)
			Position = position;
			DistanceFromEntrance = distanceFromEntrance;
			TileVolume = tileVolume;
			NormalizedDepth = normalizedDepth;
			PreferredUnusedAnchor = preferredUnusedAnchor;
			StableKey = stableKey;
			RelativeDungeonPath = relativeDungeonPath;
		}
	}
	internal readonly struct FallbackSpawnResult
	{
		public LungProp LungProp { get; }

		public Vector3 Position { get; }

		public string PlacementKey { get; }

		public string RelativeDungeonPath { get; }

		public FallbackSpawnResult(LungProp lungProp, Vector3 position, string placementKey, string relativeDungeonPath)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			LungProp = lungProp;
			Position = position;
			PlacementKey = placementKey;
			RelativeDungeonPath = relativeDungeonPath;
		}
	}
	internal static class ApparatusValidator
	{
		private const string VanillaApparatusItemName = "Apparatus";

		private static readonly FieldInfo? NormalizedPathDepthField = AccessTools.Field(typeof(TilePlacementData), "normalizedPathDepth");

		public static void Evaluate(RoundManager roundManager)
		{
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			ScanNodeSupportService.EnsureMessagingReady();
			ScanNodeSupportService.TryResolvePending(roundManager);
			if (!GuaranteeApparatusPlugin.Enabled.Value)
			{
				return;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsServer)
			{
				return;
			}
			if (!TryBuildContext(roundManager, out ValidationContext context))
			{
				GuaranteeApparatusPlugin.Verbose("Skipping apparatus validation because dungeon context was incomplete.");
				return;
			}
			DetectedCustomApparatusCandidate detectedCustomApparatusCandidate = null;
			bool flag = false;
			if (context.Classification == InteriorClassification.KnownCustom)
			{
				IKnownCustomInteriorRule knownRule = context.KnownRule;
				if (knownRule != null)
				{
					CustomApparatusDetectionResult customApparatusDetectionResult = knownRule.DetectCustomApparatus(context);
					GuaranteeApparatusPlugin.Verbose($"Known custom detector '{knownRule.DisplayName}' => detected={customApparatusDetectionResult.Detected}, reason='{customApparatusDetectionResult.Reason}'.");
					if (customApparatusDetectionResult.Detected && (Object)(object)customApparatusDetectionResult.ApparatusRoot != (Object)null)
					{
						flag = true;
						detectedCustomApparatusCandidate = new DetectedCustomApparatusCandidate(InteriorClassification.KnownCustom, knownRule.RuleId, knownRule.DisplayName, customApparatusDetectionResult.ApparatusRoot, context.GetRelativeDungeonPath(customApparatusDetectionResult.ApparatusRoot), string.IsNullOrWhiteSpace(customApparatusDetectionResult.DisplayName) ? ((Object)customApparatusDetectionResult.ApparatusRoot).name : customApparatusDetectionResult.DisplayName, customApparatusDetectionResult.Reason, 0);
						ScanNodeSupportService.EnsureCustomScanSupport(context, detectedCustomApparatusCandidate);
						GuaranteeApparatusPlugin.Log.LogInfo((object)"known custom apparatus detected");
						if (GuaranteeApparatusPlugin.RespectCustomApparatus.Value && !GuaranteeApparatusPlugin.ForceVanillaFallbackOnKnownCustomInteriors.Value)
						{
							return;
						}
					}
					goto IL_019c;
				}
			}
			if (context.Classification == InteriorClassification.UnknownCustom && UnknownCustomApparatusDiscovery.TryDiscover(context, out DetectedCustomApparatusCandidate detectedCandidate))
			{
				flag = true;
				detectedCustomApparatusCandidate = detectedCandidate;
				ScanNodeSupportOutcome scanNodeSupportOutcome = ScanNodeSupportService.EnsureCustomScanSupport(context, detectedCustomApparatusCandidate);
				GuaranteeApparatusPlugin.Log.LogInfo((object)((scanNodeSupportOutcome == ScanNodeSupportOutcome.Generated) ? "unknown custom apparatus detected; scan node generated" : "unknown custom apparatus detected"));
				if (GuaranteeApparatusPlugin.RespectUnknownCustomApparatus.Value)
				{
					return;
				}
			}
			else if (context.Classification == InteriorClassification.UnknownCustom)
			{
				GuaranteeApparatusPlugin.Log.LogInfo((object)"unknown custom apparatus NOT found; proceeding to vanilla/fallback evaluation");
			}
			goto IL_019c;
			IL_019c:
			if (TryFindValidVanillaApparatus(context, detectedCustomApparatusCandidate, out LungProp existingVanillaApparatus))
			{
				GuaranteeApparatusPlugin.Verbose("Accepted vanilla apparatus at '" + GetStableTransformPath(((Component)existingVanillaApparatus).transform) + "'.");
				GuaranteeApparatusPlugin.Log.LogInfo((object)"vanilla apparatus detected");
			}
			else
			{
				if ((context.Classification != 0 && (context.Classification != InteriorClassification.KnownCustom || !GuaranteeApparatusPlugin.ForceVanillaFallbackOnKnownCustomInteriors.Value) && (context.Classification != InteriorClassification.UnknownCustom || flag || !GuaranteeApparatusPlugin.SpawnFallbackWhenUnknownCustomHasNoDetectedApparatus.Value)) || GuaranteeApparatusPlugin.HasSpawnedFallbackThisRound)
				{
					return;
				}
				if (TrySpawnFallback(context, out var fallbackSpawnResult))
				{
					GuaranteeApparatusPlugin.HasSpawnedFallbackThisRound = true;
					GuaranteeApparatusPlugin.Verbose("Spawned fallback vanilla apparatus using placement key '" + fallbackSpawnResult.PlacementKey + "'.");
					GuaranteeApparatusPlugin.Log.LogInfo((object)"fallback vanilla apparatus spawned");
					GuaranteeApparatusPlugin.Log.LogInfo((object)("fallback vanilla apparatus location: position=" + FormatVector3(fallbackSpawnResult.Position) + ", dungeonPath='" + fallbackSpawnResult.RelativeDungeonPath + "'"));
					switch (ScanNodeSupportService.EnsureFallbackScanSupport(fallbackSpawnResult.LungProp, fallbackSpawnResult.RelativeDungeonPath))
					{
					case ScanNodeSupportOutcome.Generated:
						GuaranteeApparatusPlugin.Log.LogInfo((object)"fallback vanilla apparatus scan node generated");
						break;
					case ScanNodeSupportOutcome.AlreadyPresent:
						GuaranteeApparatusPlugin.Log.LogInfo((object)"fallback vanilla apparatus already had valid scan support");
						break;
					default:
						GuaranteeApparatusPlugin.Log.LogWarning((object)"fallback vanilla apparatus scan support could not be ensured");
						break;
					}
				}
				else
				{
					GuaranteeApparatusPlugin.Log.LogInfo((object)"skipped because no safe fallback point existed");
				}
			}
		}

		internal static string GetRelativeTransformPath(Transform transform, Transform rootTransform)
		{
			if ((Object)(object)transform == (Object)(object)rootTransform)
			{
				return string.Empty;
			}
			Stack<string> stack = new Stack<string>();
			Transform val = transform;
			while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)rootTransform)
			{
				stack.Push(((Object)val).name);
				val = val.parent;
			}
			if (!((Object)(object)val == (Object)(object)rootTransform))
			{
				return GetStableTransformPath(transform);
			}
			return string.Join("/", stack);
		}

		private static bool TryBuildContext(RoundManager roundManager, out ValidationContext context)
		{
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			context = null;
			StartOfRound instance = StartOfRound.Instance;
			if ((Object)(object)instance == (Object)null || (Object)(object)roundManager.dungeonGenerator == (Object)null || roundManager.dungeonGenerator.Generator == null)
			{
				return false;
			}
			Dungeon currentDungeon = roundManager.dungeonGenerator.Generator.CurrentDungeon;
			if ((Object)(object)currentDungeon == (Object)null)
			{
				return false;
			}
			List<Tile> list = new List<Tile>();
			foreach (Tile allTile in currentDungeon.AllTiles)
			{
				if ((Object)(object)allTile != (Object)null)
				{
					list.Add(allTile);
				}
			}
			if (list.Count == 0)
			{
				return false;
			}
			LllContext lllContext = LllReflection.Capture();
			string flowName = GetFlowName(roundManager, lllContext);
			bool isFactoryLike = IsFactoryLikeFlow(flowName);
			context = new ValidationContext(roundManager, instance, currentDungeon, list, lllContext, flowName, isFactoryLike);
			context.KnownRule = KnownCustomInteriorRules.Match(context);
			context.Classification = ((context.KnownRule != null) ? InteriorClassification.KnownCustom : ((lllContext.IsLoaded && lllContext.CurrentExtendedDungeonIsCustom) ? InteriorClassification.UnknownCustom : InteriorClassification.Vanilla));
			context.EntrancePosition = FindEntrancePosition(context);
			GuaranteeApparatusPlugin.Verbose($"Built context: flow='{context.FlowName}', factoryLike={context.IsFactoryLike}, classification={context.Classification}, " + $"tiles={context.Tiles.Count}, doors={context.Dungeon.Doors.Count}, connections={context.Dungeon.Connections.Count}.");
			return true;
		}

		private static string GetFlowName(RoundManager roundManager, LllContext lllContext)
		{
			if (!string.IsNullOrWhiteSpace(lllContext.DungeonName))
			{
				return lllContext.DungeonName;
			}
			IndoorMapType[] dungeonFlowTypes = roundManager.dungeonFlowTypes;
			if (dungeonFlowTypes != null && roundManager.currentDungeonType >= 0 && roundManager.currentDungeonType < dungeonFlowTypes.Length)
			{
				IndoorMapType val = dungeonFlowTypes[roundManager.currentDungeonType];
				if (val != null && (Object)(object)val.dungeonFlow != (Object)null && !string.IsNullOrWhiteSpace(((Object)val.dungeonFlow).name))
				{
					return ((Object)val.dungeonFlow).name;
				}
			}
			return "unknown";
		}

		private static bool IsFactoryLikeFlow(string? flowName)
		{
			if (string.IsNullOrWhiteSpace(flowName))
			{
				return false;
			}
			string[] array = GuaranteeApparatusPlugin.FactoryFlowPatterns.Value.Split(',');
			for (int i = 0; i < array.Length; i++)
			{
				string text = array[i].Trim();
				if (text.Length != 0 && flowName.Contains(text, StringComparison.OrdinalIgnoreCase))
				{
					return true;
				}
			}
			return false;
		}

		private static Vector3 FindEntrancePosition(ValidationContext context)
		{
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			List<(int, string, Vector3)> list = new List<(int, string, Vector3)>();
			foreach (EntranceTeleport item in context.FindSceneObjects<EntranceTeleport>())
			{
				if (!((Object)(object)item == (Object)null) && item.isEntranceToBuilding)
				{
					Transform val = null;
					if ((Object)(object)item.entrancePoint != (Object)null && context.IsInDungeonHierarchy(item.entrancePoint))
					{
						val = item.entrancePoint;
					}
					else if (context.IsInDungeonHierarchy(((Component)item).transform))
					{
						val = ((Component)item).transform;
					}
					else if ((Object)(object)item.exitPoint != (Object)null && context.IsInDungeonHierarchy(item.exitPoint))
					{
						val = item.exitPoint;
					}
					if (!((Object)(object)val == (Object)null))
					{
						list.Add((item.entranceId, context.GetRelativeDungeonPath(val), val.position));
					}
				}
			}
			if (list.Count > 0)
			{
				list.Sort(delegate((int EntranceId, string StablePath, Vector3 Position) left, (int EntranceId, string StablePath, Vector3 Position) right)
				{
					int num = left.EntranceId.CompareTo(right.EntranceId);
					return (num == 0) ? string.Compare(left.StablePath, right.StablePath, StringComparison.Ordinal) : num;
				});
				return list[0].Item3;
			}
			Tile val2 = null;
			string strB = string.Empty;
			for (int i = 0; i < context.Tiles.Count; i++)
			{
				Tile val3 = context.Tiles[i];
				string relativeDungeonPath = context.GetRelativeDungeonPath(((Component)val3).transform);
				if ((Object)(object)val2 == (Object)null)
				{
					val2 = val3;
					strB = relativeDungeonPath;
					continue;
				}
				float normalizedDepth = GetNormalizedDepth(val3);
				float normalizedDepth2 = GetNormalizedDepth(val2);
				if (normalizedDepth < normalizedDepth2 || (Math.Abs(normalizedDepth - normalizedDepth2) < 0.0001f && string.Compare(relativeDungeonPath, strB, StringComparison.Ordinal) < 0))
				{
					val2 = val3;
					strB = relativeDungeonPath;
				}
			}
			if (!((Object)(object)val2 != (Object)null))
			{
				return context.DungeonRootTransform.position;
			}
			Bounds bounds = val2.Bounds;
			return ((Bounds)(ref bounds)).center;
		}

		private static bool TryFindValidVanillaApparatus(ValidationContext context, DetectedCustomApparatusCandidate? detectedCustomCandidate, out LungProp? existingVanillaApparatus)
		{
			List<LungProp> list = new List<LungProp>();
			foreach (LungProp item in context.FindSceneObjects<LungProp>())
			{
				list.Add(item);
			}
			list.Sort((LungProp left, LungProp right) => string.Compare(GetStableTransformPath(((Component)left).transform), GetStableTransformPath(((Component)right).transform), StringComparison.Ordinal));
			for (int i = 0; i < list.Count; i++)
			{
				LungProp val = list[i];
				if (IsValidVanillaLungProp(context, val, detectedCustomCandidate))
				{
					existingVanillaApparatus = val;
					return true;
				}
			}
			existingVanillaApparatus = null;
			return false;
		}

		private static bool IsValidVanillaLungProp(ValidationContext context, LungProp? lungProp, DetectedCustomApparatusCandidate? detectedCustomCandidate)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: 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_00de: Unknown result type (might be due to invalid IL or missing references)
			//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_0106: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)lungProp == (Object)null))
			{
				Scene scene = ((Component)lungProp).gameObject.scene;
				if (((Scene)(ref scene)).IsValid() && ((Component)lungProp).gameObject.activeInHierarchy && ((Behaviour)lungProp).isActiveAndEnabled && !((GrabbableObject)lungProp).deactivated)
				{
					if ((Object)(object)lungProp.roundManager == (Object)null || lungProp.roundManager != context.RoundManager)
					{
						return false;
					}
					if (!IsVanillaApparatusItem(((GrabbableObject)lungProp).itemProperties))
					{
						return false;
					}
					if (context.KnownRule != null && context.KnownRule.IsCustomControlledLungProp(context, lungProp))
					{
						return false;
					}
					if (detectedCustomCandidate != null && detectedCustomCandidate.ControlsTransform(((Component)lungProp).transform))
					{
						return false;
					}
					Bounds dungeonBounds;
					if (!context.IsInDungeonHierarchy(((Component)lungProp).transform) && !context.IsInDungeon(((Component)lungProp).transform.position))
					{
						dungeonBounds = context.DungeonBounds;
						if (!((Bounds)(ref dungeonBounds)).Contains(((Component)lungProp).transform.position))
						{
							return false;
						}
					}
					EvaluateReachability(context, ((Component)lungProp).transform.position, out var available, out var reachable);
					if (available && !reachable)
					{
						return false;
					}
					if (!available)
					{
						dungeonBounds = context.DungeonBounds;
						if (!((Bounds)(ref dungeonBounds)).Contains(((Component)lungProp).transform.position) || !((Component)lungProp).gameObject.activeInHierarchy)
						{
							return false;
						}
					}
					return true;
				}
			}
			return false;
		}

		private static bool IsVanillaApparatusItem(Item? item)
		{
			if ((Object)(object)item != (Object)null && string.Equals(item.itemName, "Apparatus", StringComparison.OrdinalIgnoreCase) && (Object)(object)item.spawnPrefab != (Object)null && (Object)(object)item.spawnPrefab.GetComponent<LungProp>() != (Object)null)
			{
				return LllReflection.IsVanillaItem(item);
			}
			return false;
		}

		private static bool TrySpawnFallback(ValidationContext context, out FallbackSpawnResult fallbackSpawnResult)
		{
			//IL_007d: 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_0080: 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_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: 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_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0131: Unknown result type (might be due to invalid IL or missing references)
			//IL_0133: Unknown result type (might be due to invalid IL or missing references)
			//IL_01de: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fe: 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_0208: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ee: 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_026d: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b8: Unknown result type (might be due to invalid IL or missing references)
			fallbackSpawnResult = default(FallbackSpawnResult);
			if (!ResolveVanillaApparatusItem(context.StartOfRound, out Item apparatusItem) || (Object)(object)apparatusItem?.spawnPrefab == (Object)null)
			{
				GuaranteeApparatusPlugin.Verbose("Could not resolve the real vanilla Apparatus item.");
				return false;
			}
			if (!TryFindFallbackPosition(context, out Vector3 position, out string stableKey, out string relativeDungeonPath))
			{
				return false;
			}
			GameObject mapPropsContainer = context.RoundManager.mapPropsContainer;
			Transform val = ((mapPropsContainer != null) ? mapPropsContainer.transform : null) ?? context.RoundManager.spawnedScrapContainer ?? ((Component)context.RoundManager).transform;
			Vector3 val2 = position;
			Quaternion val3 = Quaternion.identity;
			bool flag = false;
			Transform val4 = null;
			if (VanillaApparatusSetpieceService.TryCreateServerSetpiece(context.RoundManager, val, position, out var result))
			{
				flag = true;
				val4 = result.DockAnchor;
				val2 = result.DockPosition;
				val3 = result.DockRotation;
				GuaranteeApparatusPlugin.Log.LogInfo((object)("fallback vanilla apparatus machine spawned: sourceFlow='" + result.SourceFlowName + "', tile='" + result.TilePrefabName + "', machineRoot='" + result.MachineRootPath + "', dockPosition=" + FormatVector3(val2)));
			}
			else
			{
				GuaranteeApparatusPlugin.Log.LogWarning((object)"fallback vanilla apparatus machine could not be resolved; using loose vanilla fallback");
			}
			GameObject val5 = Object.Instantiate<GameObject>(apparatusItem.spawnPrefab, val2, val3, val);
			if ((Object)(object)val5 == (Object)null)
			{
				return false;
			}
			LungProp component = val5.GetComponent<LungProp>();
			NetworkObject component2 = val5.GetComponent<NetworkObject>();
			if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null)
			{
				Object.Destroy((Object)(object)val5);
				return false;
			}
			int num = CalculateScrapValue(apparatusItem, context.StartOfRound.randomMapSeed, stableKey);
			((GrabbableObject)component).itemProperties = apparatusItem;
			component.roundManager = context.RoundManager;
			((GrabbableObject)component).isInFactory = true;
			((GrabbableObject)component).isInElevator = false;
			((GrabbableObject)component).isInShipRoom = false;
			component.isLungDocked = flag;
			component.isLungDockedInElevator = false;
			component.isLungPowered = flag;
			((GrabbableObject)component).deactivated = false;
			((GrabbableObject)component).targetFloorPosition = val2;
			((GrabbableObject)component).startFallingPosition = val2 + Vector3.up * (flag ? 0.1f : 2f);
			((GrabbableObject)component).scrapValue = num;
			((GrabbableObject)component).SetScrapValue(num);
			if (!component2.IsSpawned)
			{
				component2.Spawn(false);
			}
			if (flag && (Object)(object)val4 != (Object)null)
			{
				DockedFallbackApparatusController.Attach(component, val4, result.DockVisualRoot);
				((GrabbableObject)component).parentObject = val4;
				((Component)component).transform.SetPositionAndRotation(val4.position, val4.rotation);
				if ((Object)(object)((GrabbableObject)component).propBody != (Object)null)
				{
					((GrabbableObject)component).propBody.isKinematic = true;
					((GrabbableObject)component).propBody.useGravity = false;
					((GrabbableObject)component).propBody.velocity = Vector3.zero;
					((GrabbableObject)component).propBody.angularVelocity = Vector3.zero;
				}
			}
			else
			{
				((GrabbableObject)component).FallToGround(true, true, val2);
			}
			FallbackApparatusFacilityMeltdownBridge.Attach(component);
			RoundManager roundManager = context.RoundManager;
			roundManager.totalScrapValueInLevel += (float)num;
			fallbackSpawnResult = new FallbackSpawnResult(component, val2, stableKey, relativeDungeonPath);
			return true;
		}

		private static bool ResolveVanillaApparatusItem(StartOfRound startOfRound, out Item? apparatusItem)
		{
			apparatusItem = null;
			if ((Object)(object)startOfRound.allItemsList == (Object)null || startOfRound.allItemsList.itemsList == null)
			{
				return false;
			}
			List<Item> itemsList = startOfRound.allItemsList.itemsList;
			for (int i = 0; i < itemsList.Count; i++)
			{
				Item val = itemsList[i];
				if (IsVanillaApparatusItem(val))
				{
					apparatusItem = val;
					return true;
				}
			}
			return false;
		}

		private static int CalculateScrapValue(Item apparatusItem, int mapSeed, string stableKey)
		{
			int num = Math.Max(0, apparatusItem.minValue);
			int num2 = Math.Max(num, apparatusItem.maxValue);
			if (num2 <= num)
			{
				return num;
			}
			return new Random(mapSeed ^ GetStableHash(stableKey) ^ 0x5F3759DF).Next(num, num2 + 1);
		}

		private static bool TryFindFallbackPosition(ValidationContext context, out Vector3 position, out string stableKey, out string relativeDungeonPath)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: 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_0039: Unknown result type (might be due to invalid IL or missing references)
			List<SpawnCandidate> list = new List<SpawnCandidate>();
			BuildAnchorCandidates(context, list);
			BuildTileCandidates(context, list);
			SortCandidates(list);
			for (int i = 0; i < list.Count; i++)
			{
				SpawnCandidate spawnCandidate = list[i];
				if (TryValidateSpawnPosition(context, spawnCandidate.Position, out var validatedPosition))
				{
					position = validatedPosition;
					stableKey = spawnCandidate.StableKey;
					relativeDungeonPath = spawnCandidate.RelativeDungeonPath;
					return true;
				}
			}
			position = default(Vector3);
			stableKey = string.Empty;
			relativeDungeonPath = string.Empty;
			return false;
		}

		private static void BuildAnchorCandidates(ValidationContext context, List<SpawnCandidate> candidates)
		{
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			ValidationContext context2 = context;
			RandomScrapSpawn[] array = context2.FindDungeonComponents<RandomScrapSpawn>();
			Array.Sort(array, (RandomScrapSpawn left, RandomScrapSpawn right) => string.Compare(context2.GetRelativeDungeonPath(((Component)left).transform), context2.GetRelativeDungeonPath(((Component)right).transform), StringComparison.Ordinal));
			HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal);
			foreach (RandomScrapSpawn val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Scene scene = ((Component)val).gameObject.scene;
				if (!((Scene)(ref scene)).IsValid() || !context2.IsInDungeonHierarchy(((Component)val).transform))
				{
					continue;
				}
				Vector3 position = ((Component)val).transform.position;
				if (context2.TryFindContainingTile(position, out Tile tile, out Bounds containingBounds) && !((Object)(object)tile == (Object)null))
				{
					string relativeDungeonPath = context2.GetRelativeDungeonPath(((Component)val).transform);
					string text = "anchor:" + relativeDungeonPath;
					if (hashSet.Add(text))
					{
						candidates.Add(new SpawnCandidate(position, Vector3.Distance(context2.EntrancePosition, position), ((Bounds)(ref containingBounds)).size.x * ((Bounds)(ref containingBounds)).size.y * ((Bounds)(ref containingBounds)).size.z, GetNormalizedDepth(tile), !val.spawnUsed, text, relativeDungeonPath));
					}
				}
			}
		}

		private static void BuildTileCandidates(ValidationContext context, List<SpawnCandidate> candidates)
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f0: 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_011b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			//IL_0137: Unknown result type (might be due to invalid IL or missing references)
			//IL_0139: 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)
			//IL_0152: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0160: Unknown result type (might be due to invalid IL or missing references)
			//IL_016c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Unknown result type (might be due to invalid IL or missing references)
			List<(Tile, string)> list = new List<(Tile, string)>(context.Tiles.Count);
			for (int i = 0; i < context.Tiles.Count; i++)
			{
				Tile val = context.Tiles[i];
				list.Add((val, context.GetRelativeDungeonPath(((Component)val).transform)));
			}
			list.Sort(((Tile Tile, string Path) left, (Tile Tile, string Path) right) => string.Compare(left.Path, right.Path, StringComparison.Ordinal));
			for (int j = 0; j < list.Count; j++)
			{
				Tile item = list[j].Item1;
				Bounds tileBounds = context.GetTileBounds(item);
				float num = Math.Max(1.5f, Math.Min(((Bounds)(ref tileBounds)).extents.x, ((Bounds)(ref tileBounds)).extents.z) * 0.75f);
				int seed = context.StartOfRound.randomMapSeed ^ GetStableHash("tile:" + list[j].Item2);
				Vector3 val2 = context.RoundManager.GetRandomNavMeshPositionInBoxPredictable(((Bounds)(ref tileBounds)).center, num, context.RoundManager.navHit, new Random(seed), context.RoundManager.collisionsMask, Math.Max(2f, ((Bounds)(ref tileBounds)).extents.y + 2f));
				if (val2 == Vector3.zero)
				{
					val2 = ((Bounds)(ref tileBounds)).center;
				}
				candidates.Add(new SpawnCandidate(val2, Vector3.Distance(context.EntrancePosition, val2), ((Bounds)(ref tileBounds)).size.x * ((Bounds)(ref tileBounds)).size.y * ((Bounds)(ref tileBounds)).size.z, GetNormalizedDepth(item), preferredUnusedAnchor: false, "tile:" + list[j].Item2, list[j].Item2));
			}
		}

		private static void SortCandidates(List<SpawnCandidate> candidates)
		{
			candidates.Sort(delegate(SpawnCandidate left, SpawnCandidate right)
			{
				int num = right.PreferredUnusedAnchor.CompareTo(left.PreferredUnusedAnchor);
				if (num != 0)
				{
					return num;
				}
				int num2 = right.DistanceFromEntrance.CompareTo(left.DistanceFromEntrance);
				if (num2 != 0)
				{
					return num2;
				}
				int num3 = right.NormalizedDepth.CompareTo(left.NormalizedDepth);
				if (num3 != 0)
				{
					return num3;
				}
				int num4 = right.TileVolume.CompareTo(left.TileVolume);
				return (num4 != 0) ? num4 : string.Compare(left.StableKey, right.StableKey, StringComparison.Ordinal);
			});
		}

		private static bool TryValidateSpawnPosition(ValidationContext context, Vector3 candidatePosition, out Vector3 validatedPosition)
		{
			//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_0007: 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_0015: 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_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: 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_005b: 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_0045: 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)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: 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_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			validatedPosition = candidatePosition;
			Vector3 val = candidatePosition;
			int collidersAndRoomMask = context.StartOfRound.collidersAndRoomMask;
			RaycastHit val2 = default(RaycastHit);
			if (Physics.Raycast(candidatePosition + Vector3.up * 6f, Vector3.down, ref val2, 16f, collidersAndRoomMask))
			{
				val = ((RaycastHit)(ref val2)).point + Vector3.up * 0.15f;
			}
			if (!context.TryFindContainingTile(val, out Tile _, out Bounds containingBounds))
			{
				Bounds dungeonBounds = context.DungeonBounds;
				if (!((Bounds)(ref dungeonBounds)).Contains(val))
				{
					return false;
				}
				containingBounds = ValidationContext.ExpandedBounds(context.DungeonBounds, 1f);
			}
			if (Vector3.Distance(context.EntrancePosition, val) < GuaranteeApparatusPlugin.MinimumEntranceDistance.Value)
			{
				return false;
			}
			EvaluateReachability(context, val, out var available, out var reachable);
			if (available && !reachable)
			{
				return false;
			}
			if (!available && !((Bounds)(ref containingBounds)).Contains(val))
			{
				return false;
			}
			validatedPosition = val;
			return true;
		}

		private static void EvaluateReachability(ValidationContext context, Vector3 position, out bool available, out bool reachable)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: 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_0036: Expected O, but got Unknown
			//IL_0039: 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_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Invalid comparison between Unknown and I4
			available = false;
			reachable = false;
			NavMeshHit val = default(NavMeshHit);
			NavMeshHit val2 = default(NavMeshHit);
			if (NavMesh.SamplePosition(position, ref val, 2f, -1) && NavMesh.SamplePosition(context.EntrancePosition, ref val2, 3f, -1))
			{
				available = true;
				NavMeshPath val3 = new NavMeshPath();
				reachable = NavMesh.CalculatePath(((NavMeshHit)(ref val2)).position, ((NavMeshHit)(ref val)).position, -1, val3) && (int)val3.status == 0;
			}
		}

		private static float GetNormalizedDepth(Tile tile)
		{
			object placement = tile.Placement;
			object obj = NormalizedPathDepthField?.GetValue(placement);
			if (obj is float)
			{
				return (float)obj;
			}
			return 0f;
		}

		private static string GetStableTransformPath(Transform transform)
		{
			Stack<string> stack = new Stack<string>();
			Transform val = transform;
			while ((Object)(object)val != (Object)null)
			{
				stack.Push(((Object)val).name);
				val = val.parent;
			}
			return string.Join("/", stack);
		}

		private static int GetStableHash(string value)
		{
			int num = -2128831035;
			for (int i = 0; i < value.Length; i++)
			{
				num ^= value[i];
				num *= 16777619;
			}
			return num;
		}

		private static string FormatVector3(Vector3 vector)
		{
			//IL_0005: 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_001b: Unknown result type (might be due to invalid IL or missing references)
			return $"({vector.x:F2}, {vector.y:F2}, {vector.z:F2})";
		}
	}
	internal readonly struct CustomApparatusDetectionResult
	{
		public bool Detected { get; }

		public string Reason { get; }

		public Transform? ApparatusRoot { get; }

		public string DisplayName { get; }

		public CustomApparatusDetectionResult(bool detected, string reason, Transform? apparatusRoot = null, string? displayName = null)
		{
			Detected = detected;
			Reason = reason;
			ApparatusRoot = apparatusRoot;
			DisplayName = displayName ?? ((apparatusRoot != null) ? ((Object)apparatusRoot).name : null) ?? string.Empty;
		}
	}
	internal interface IKnownCustomInteriorRule
	{
		string RuleId { get; }

		string DisplayName { get; }

		bool Matches(ValidationContext context);

		CustomApparatusDetectionResult DetectCustomApparatus(ValidationContext context);

		bool IsCustomControlledLungProp(ValidationContext context, LungProp lungProp);
	}
	internal static class KnownCustomInteriorRules
	{
		private static readonly IKnownCustomInteriorRule[] Rules = new IKnownCustomInteriorRule[1]
		{
			new LcOfficeRule()
		};

		public static IKnownCustomInteriorRule? Match(ValidationContext context)
		{
			IKnownCustomInteriorRule[] rules = Rules;
			foreach (IKnownCustomInteriorRule knownCustomInteriorRule in rules)
			{
				if (knownCustomInteriorRule.Matches(context))
				{
					return knownCustomInteriorRule;
				}
			}
			return null;
		}
	}
	internal sealed class LcOfficeRule : IKnownCustomInteriorRule
	{
		private const string PluginGuid = "Piggy.LCOffice";

		private const string OfficeModName = "LC Office";

		private const string OfficeElevatorTypeName = "LCOffice.Components.ElevatorSystem";

		private const string OfficeInsertedFieldName = "inserted";

		private const string OfficeSystemPropertyName = "System";

		private const string OfficeLungParentFieldName = "lungParent";

		private static readonly Type? ElevatorSystemType = AccessTools.TypeByName("LCOffice.Components.ElevatorSystem");

		private static readonly PropertyInfo? ElevatorSystemProperty = AccessTools.Property(ElevatorSystemType, "System");

		private static readonly FieldInfo? ElevatorInsertedField = AccessTools.Field(ElevatorSystemType, "inserted");

		private static readonly FieldInfo? ElevatorLungParentField = AccessTools.Field(ElevatorSystemType, "lungParent");

		public string RuleId => "lc_office";

		public string DisplayName => "LC_Office";

		public bool Matches(ValidationContext context)
		{
			if (!Chainloader.PluginInfos.ContainsKey("Piggy.LCOffice"))
			{
				return false;
			}
			if (context.LllContext.IsLoaded && (MatchesOfficeString(context.LllContext.DungeonModName) || MatchesOfficeString(context.LllContext.DungeonUniqueId) || MatchesOfficeString(context.LllContext.DungeonName)))
			{
				return true;
			}
			if (TryFindOfficeItemRoot(context, out Transform officeRoot))
			{
				return (Object)(object)officeRoot != (Object)null;
			}
			if (TryGetElevatorSystemComponent(out Component elevatorSystem) && (Object)(object)elevatorSystem != (Object)null)
			{
				return context.IsInDungeonHierarchy(elevatorSystem.transform);
			}
			return false;
		}

		public CustomApparatusDetectionResult DetectCustomApparatus(ValidationContext context)
		{
			if (TryGetElevatorSystemComponent(out Component elevatorSystem) && (Object)(object)elevatorSystem != (Object)null && context.IsInDungeonHierarchy(elevatorSystem.transform))
			{
				return new CustomApparatusDetectionResult(detected: true, "active LC_Office elevator system marker found", elevatorSystem.transform, ((Object)elevatorSystem).name);
			}
			if (TryFindOfficeItemRoot(context, out Transform officeRoot) && (Object)(object)officeRoot != (Object)null)
			{
				return new CustomApparatusDetectionResult(detected: true, "LC_Office apparatus item found in dungeon", officeRoot, ((Object)officeRoot).name);
			}
			return new CustomApparatusDetectionResult(detected: false, "no LC_Office custom apparatus markers found");
		}

		public bool IsCustomControlledLungProp(ValidationContext context, LungProp lungProp)
		{
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			if (IsOfficeApparatusItem(((GrabbableObject)lungProp).itemProperties))
			{
				return true;
			}
			if (!TryGetElevatorSystemComponent(out Component elevatorSystem))
			{
				return false;
			}
			object? obj = ElevatorInsertedField?.GetValue(elevatorSystem);
			LungProp val = (LungProp)((obj is LungProp) ? obj : null);
			if (val != null && val == lungProp)
			{
				return true;
			}
			object? obj2 = ElevatorLungParentField?.GetValue(elevatorSystem);
			Transform val2 = (Transform)((obj2 is Transform) ? obj2 : null);
			if (val2 != null)
			{
				if (((Component)lungProp).transform.IsChildOf(val2))
				{
					return true;
				}
				if (Vector3.Distance(((Component)lungProp).transform.position, val2.position) <= 1.5f)
				{
					return true;
				}
			}
			return false;
		}

		private static bool TryGetElevatorSystemComponent(out Component? elevatorSystem)
		{
			//IL_0026: 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)
			elevatorSystem = null;
			object? obj = ElevatorSystemProperty?.GetValue(null);
			Component val = (Component)((obj is Component) ? obj : null);
			if (val == null)
			{
				return false;
			}
			Scene scene = val.gameObject.scene;
			if (!((Scene)(ref scene)).IsValid())
			{
				return false;
			}
			elevatorSystem = val;
			return true;
		}

		private static bool TryFindOfficeItemRoot(ValidationContext context, out Transform? officeRoot)
		{
			LungProp[] array = context.FindDungeonComponents<LungProp>();
			foreach (LungProp val in array)
			{
				if (IsOfficeApparatusItem(((GrabbableObject)val).itemProperties))
				{
					officeRoot = ((Component)val).transform;
					return true;
				}
			}
			GrabbableObject[] array2 = context.FindDungeonComponents<GrabbableObject>();
			foreach (GrabbableObject val2 in array2)
			{
				if (IsOfficeApparatusItem(val2.itemProperties))
				{
					officeRoot = ((Component)val2).transform;
					return true;
				}
			}
			officeRoot = null;
			return false;
		}

		private static bool IsOfficeApparatusItem(Item? item)
		{
			if ((Object)(object)item == (Object)null)
			{
				return false;
			}
			string text = item.itemName ?? string.Empty;
			string text2 = ((Object)item).name ?? string.Empty;
			if (!text.Contains("apparatus", StringComparison.OrdinalIgnoreCase) && !text2.Contains("apparatus", StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			if (text.Contains("office", StringComparison.OrdinalIgnoreCase) || text2.Contains("office", StringComparison.OrdinalIgnoreCase) || text2.Contains("upturned", StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			if (LllReflection.TryGetExtendedItemMetadata(item, out string modName, out string uniqueId))
			{
				if (!MatchesOfficeString(modName))
				{
					return MatchesOfficeString(uniqueId);
				}
				return true;
			}
			return false;
		}

		private static bool MatchesOfficeString(string? value)
		{
			if (string.IsNullOrWhiteSpace(value))
			{
				return false;
			}
			if (!value.Contains("LC Office", StringComparison.OrdinalIgnoreCase))
			{
				return value.Contains("office", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}
	}
	internal sealed class DockedFallbackApparatusController : MonoBehaviour
	{
		private LungProp? _lungProp;

		private Transform? _dockAnchor;

		private Transform? _dockVisualRoot;

		private bool _released;

		public static void Attach(LungProp lungProp, Transform dockAnchor, Transform dockVisualRoot)
		{
			DockedFallbackApparatusController obj = ((Component)lungProp).GetComponent<DockedFallbackApparatusController>() ?? ((Component)lungProp).gameObject.AddComponent<DockedFallbackApparatusController>();
			obj._lungProp = lungProp;
			obj._dockAnchor = dockAnchor;
			obj._dockVisualRoot = dockVisualRoot;
			obj.LockToDock();
		}

		private void LateUpdate()
		{
			if ((Object)(object)_lungProp == (Object)null || (Object)(object)_dockAnchor == (Object)null)
			{
				Object.Destroy((Object)(object)this);
			}
			else if (ShouldRelease())
			{
				ReleaseDock();
			}
			else
			{
				LockToDock();
			}
		}

		private bool ShouldRelease()
		{
			if ((Object)(object)_lungProp == (Object)null || (Object)(object)_dockAnchor == (Object)null)
			{
				return true;
			}
			if (!_lungProp.isLungDocked)
			{
				return true;
			}
			if (((GrabbableObject)_lungProp).isHeld || (Object)(object)((GrabbableObject)_lungProp).playerHeldBy != (Object)null)
			{
				return true;
			}
			if ((Object)(object)((GrabbableObject)_lungProp).parentObject != (Object)null)
			{
				return (Object)(object)((GrabbableObject)_lungProp).parentObject != (Object)(object)_dockAnchor;
			}
			return false;
		}

		private void LockToDock()
		{
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_lungProp == (Object)null) && !((Object)(object)_dockAnchor == (Object)null))
			{
				((GrabbableObject)_lungProp).parentObject = _dockAnchor;
				((Component)_lungProp).transform.SetPositionAndRotation(_dockAnchor.position, _dockAnchor.rotation);
				if ((Object)(object)((GrabbableObject)_lungProp).propBody != (Object)null)
				{
					((GrabbableObject)_lungProp).propBody.isKinematic = true;
					((GrabbableObject)_lungProp).propBody.useGravity = false;
					((GrabbableObject)_lungProp).propBody.velocity = Vector3.zero;
					((GrabbableObject)_lungProp).propBody.angularVelocity = Vector3.zero;
				}
				((GrabbableObject)_lungProp).EnableItemMeshes(false);
				if ((Object)(object)_dockVisualRoot != (Object)null)
				{
					((Component)_dockVisualRoot).gameObject.SetActive(true);
				}
			}
		}

		private void ReleaseDock()
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_lungProp != (Object)null)
			{
				if ((Object)(object)((GrabbableObject)_lungProp).parentObject == (Object)(object)_dockAnchor)
				{
					((GrabbableObject)_lungProp).parentObject = null;
				}
				if ((Object)(object)((GrabbableObject)_lungProp).propBody != (Object)null)
				{
					((GrabbableObject)_lungProp).propBody.isKinematic = false;
					((GrabbableObject)_lungProp).propBody.useGravity = true;
					((GrabbableObject)_lungProp).propBody.velocity = Vector3.zero;
					((GrabbableObject)_lungProp).propBody.angularVelocity = Vector3.zero;
				}
				((GrabbableObject)_lungProp).EnableItemMeshes(true);
			}
			if (!_released && (Object)(object)_dockVisualRoot != (Object)null)
			{
				((Component)_dockVisualRoot).gameObject.SetActive(false);
			}
			_released = true;
			Object.Destroy((Object)(object)this);
		}
	}
	internal sealed class FallbackApparatusFacilityMeltdownBridge : MonoBehaviour
	{
		private LungProp? _lungProp;

		private bool _checked;

		public static void Attach(LungProp lungProp)
		{
			if (FacilityMeltdownCompatibility.IsLoaded)
			{
				(((Component)lungProp).GetComponent<FallbackApparatusFacilityMeltdownBridge>() ?? ((Component)lungProp).gameObject.AddComponent<FallbackApparatusFacilityMeltdownBridge>())._lungProp = lungProp;
			}
		}

		private void Update()
		{
			if (!_checked)
			{
				if ((Object)(object)_lungProp == (Object)null)
				{
					Object.Destroy((Object)(object)this);
				}
				else if (((GrabbableObject)_lungProp).hasBeenHeld || ((GrabbableObject)_lungProp).isHeld || !((Object)(object)((GrabbableObject)_lungProp).playerHeldBy == (Object)null))
				{
					_checked = true;
					FacilityMeltdownCompatibility.TryBeginMeltdownForFallback(_lungProp);
					Object.Destroy((Object)(object)this);
				}
			}
		}
	}
	internal static class FacilityMeltdownCompatibility
	{
		private const string FacilityMeltdownGuid = "me.loaforc.facilitymeltdown";

		private static readonly Type? MeltdownApiType = AccessTools.TypeByName("FacilityMeltdown.API.MeltdownAPI");

		private static readonly Type? MeltdownPluginType = AccessTools.TypeByName("FacilityMeltdown.MeltdownPlugin");

		private static readonly Type? MeltdownAssetsType = AccessTools.TypeByName("FacilityMeltdown.MeltdownAssets");

		private static readonly Type? MeltdownHandlerType = AccessTools.TypeByName("FacilityMeltdown.MeltdownSequence.Behaviours.MeltdownHandler");

		private static readonly PropertyInfo? MeltdownStartedProperty = AccessTools.Property(MeltdownApiType, "MeltdownStarted");

		private static readonly PropertyInfo? AssetsProperty = AccessTools.Property(MeltdownPluginType, "assets");

		private static readonly PropertyInfo? MeltdownHandlerPrefabProperty = AccessTools.Property(MeltdownAssetsType, "meltdownHandlerPrefab");

		private static readonly FieldInfo? CausingLungPropField = AccessTools.Field(MeltdownHandlerType, "causingLungProp");

		public static bool IsLoaded
		{
			get
			{
				if (Chainloader.PluginInfos.ContainsKey("me.loaforc.facilitymeltdown") && MeltdownStartedProperty != null && AssetsProperty != null && MeltdownHandlerPrefabProperty != null && MeltdownHandlerType != null)
				{
					return CausingLungPropField != null;
				}
				return false;
			}
		}

		public static void TryBeginMeltdownForFallback(LungProp lungProp)
		{
			if (!IsLoaded || (Object)(object)lungProp == (Object)null)
			{
				return;
			}
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsServer || GetMeltdownStarted())
			{
				return;
			}
			object? obj = SafeGetValue(SafeGetValue(null, AssetsProperty), MeltdownHandlerPrefabProperty);
			GameObject val = (GameObject)((obj is GameObject) ? obj : null);
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			GameObject val2 = Object.Instantiate<GameObject>(val);
			Component component = val2.GetComponent(MeltdownHandlerType);
			NetworkObject component2 = val2.GetComponent<NetworkObject>();
			if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null)
			{
				Object.Destroy((Object)(object)val2);
				return;
			}
			CausingLungPropField.SetValue(component, lungProp);
			if (!component2.IsSpawned)
			{
				component2.Spawn(false);
			}
			GuaranteeApparatusPlugin.Verbose("Facility Meltdown compatibility bridge spawned a meltdown handler for fallback apparatus pickup.");
		}

		private static bool GetMeltdownStarted()
		{
			try
			{
				object obj = SafeGetValue(null, MeltdownStartedProperty);
				bool flag = default(bool);
				int num;
				if (obj is bool)
				{
					flag = (bool)obj;
					num = 1;
				}
				else
				{
					num = 0;
				}
				return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
			}
			catch
			{
				return false;
			}
		}

		private static object? SafeGetValue(object? instance, PropertyInfo? property)
		{
			if (property == null)
			{
				return null;
			}
			try
			{
				return property.GetValue(instance);
			}
			catch
			{
				return null;
			}
		}
	}
	internal sealed class LllContext
	{
		public bool IsLoaded { get; set; }

		public bool MetadataUnreliable { get; set; }

		public object? CurrentExtendedDungeon { get; set; }

		public object? CurrentExtendedLevel { get; set; }

		public bool CurrentExtendedDungeonIsCustom { get; set; }

		public string? DungeonModName { get; set; }

		public string? DungeonAuthorName { get; set; }

		public string? DungeonUniqueId { get; set; }

		public string? DungeonName { get; set; }
	}
	internal static class LllReflection
	{
		private const string LllGuid = "imabatby.lethallevelloader";

		private static readonly Type? DungeonManagerType = AccessTools.TypeByName("LethalLevelLoader.DungeonManager");

		private static readonly Type? LevelManagerType = AccessTools.TypeByName("LethalLevelLoader.LevelManager");

		private static readonly Type? PatchedContentType = AccessTools.TypeByName("LethalLevelLoader.PatchedContent");

		private static readonly Type? ExtendedContentType = AccessTools.TypeByName("LethalLevelLoader.ExtendedContent");

		private static readonly Type? ExtendedDungeonFlowType = AccessTools.TypeByName("LethalLevelLoader.ExtendedDungeonFlow");

		private static readonly PropertyInfo? CurrentExtendedDungeonProperty = AccessTools.Property(DungeonManagerType, "CurrentExtendedDungeonFlow");

		private static readonly PropertyInfo? CurrentExtendedLevelProperty = AccessTools.Property(LevelManagerType, "CurrentExtendedLevel");

		private static readonly PropertyInfo? VanillaExtendedDungeonFlowsProperty = AccessTools.Property(PatchedContentType, "VanillaExtendedDungeonFlows");

		private static readonly PropertyInfo? VanillaModProperty = AccessTools.Property(PatchedContentType, "VanillaMod");

		private static readonly FieldInfo? ExtendedItemDictionaryField = AccessTools.Field(PatchedContentType, "ExtendedItemDictionary");

		private static readonly PropertyInfo? ModNameProperty = AccessTools.Property(ExtendedContentType, "ModName");

		private static readonly PropertyInfo? AuthorNameProperty = AccessTools.Property(ExtendedContentType, "AuthorName");

		private static readonly PropertyInfo? UniqueIdentificationNameProperty = AccessTools.Property(ExtendedContentType, "UniqueIdentificationName");

		private static readonly PropertyInfo? DungeonNameProperty = AccessTools.Property(ExtendedDungeonFlowType, "DungeonName");

		public static bool IsLoaded
		{
			get
			{
				if (Chainloader.PluginInfos.ContainsKey("imabatby.lethallevelloader") && DungeonManagerType != null)
				{
					return PatchedContentType != null;
				}
				return false;
			}
		}

		public static LllContext Capture()
		{
			if (!IsLoaded)
			{
				return new LllContext
				{
					IsLoaded = false
				};
			}
			bool metadataUnreliable = false;
			object obj = TryGetPropertyValue(null, CurrentExtendedDungeonProperty, "CurrentExtendedDungeonProperty", ref metadataUnreliable);
			object currentExtendedLevel = TryGetPropertyValue(null, CurrentExtendedLevelProperty, "CurrentExtendedLevelProperty", ref metadataUnreliable);
			string contentString = GetContentString(obj, ModNameProperty, "ModNameProperty", ref metadataUnreliable);
			string contentString2 = GetContentString(obj, AuthorNameProperty, "AuthorNameProperty", ref metadataUnreliable);
			string contentString3 = GetContentString(obj, UniqueIdentificationNameProperty, "UniqueIdentificationNameProperty", ref metadataUnreliable);
			string contentString4 = GetContentString(obj, DungeonNameProperty, "DungeonNameProperty", ref metadataUnreliable);
			return new LllContext
			{
				IsLoaded = true,
				MetadataUnreliable = metadataUnreliable,
				CurrentExtendedDungeon = obj,
				CurrentExtendedLevel = currentExtendedLevel,
				CurrentExtendedDungeonIsCustom = (!metadataUnreliable && IsCustomExtendedDungeon(obj)),
				DungeonModName = contentString,
				DungeonAuthorName = contentString2,
				DungeonUniqueId = contentString3,
				DungeonName = contentString4
			};
		}

		public static bool TryGetExtendedItemMetadata(Item? item, out string? modName, out string? uniqueId)
		{
			modName = null;
			uniqueId = null;
			if (!IsLoaded || (Object)(object)item == (Object)null || ExtendedItemDictionaryField == null)
			{
				return false;
			}
			object value;
			try
			{
				value = ExtendedItemDictionaryField.GetValue(null);
			}
			catch
			{
				return false;
			}
			if (!(value is IDictionary dictionary) || !dictionary.Contains(item))
			{
				return false;
			}
			object obj2 = dictionary[item];
			if (obj2 == null)
			{
				return false;
			}
			bool metadataUnreliable = false;
			modName = GetContentString(obj2, ModNameProperty, "ModNameProperty", ref metadataUnreliable);
			uniqueId = GetContentString(obj2, UniqueIdentificationNameProperty, "UniqueIdentificationNameProperty", ref metadataUnreliable);
			return true;
		}

		public static bool IsVanillaItem(Item? item)
		{
			if ((Object)(object)item == (Object)null)
			{
				return false;
			}
			if (!TryGetExtendedItemMetadata(item, out string modName, out string _))
			{
				return true;
			}
			string vanillaModName = GetVanillaModName();
			if (!string.IsNullOrWhiteSpace(vanillaModName))
			{
				return string.Equals(modName, vanillaModName, StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static bool IsCustomExtendedDungeon(object? currentExtendedDungeon)
		{
			if (currentExtendedDungeon == null || VanillaExtendedDungeonFlowsProperty == null)
			{
				return false;
			}
			bool metadataUnreliable = false;
			if (!(TryGetPropertyValue(null, VanillaExtendedDungeonFlowsProperty, "VanillaExtendedDungeonFlowsProperty", ref metadataUnreliable) is IEnumerable enumerable))
			{
				return false;
			}
			try
			{
				foreach (object item in enumerable)
				{
					if (item == currentExtendedDungeon)
					{
						return false;
					}
				}
			}
			catch
			{
				return false;
			}
			return true;
		}

		private static string? GetVanillaModName()
		{
			bool metadataUnreliable = false;
			object? obj = TryGetPropertyValue(null, VanillaModProperty, "VanillaModProperty", ref metadataUnreliable);
			PropertyInfo property = AccessTools.Property(obj?.GetType(), "ModName");
			return GetContentString(obj, property, "VanillaMod.ModName", ref metadataUnreliable);
		}

		private static string? GetContentString(object? instance, PropertyInfo? property, string propertyName, ref bool metadataUnreliable)
		{
			return TryGetPropertyValue(instance, property, propertyName, ref metadataUnreliable) as string;
		}

		private static object? TryGetPropertyValue(object? instance, PropertyInfo? property, string propertyName, ref bool metadataUnreliable)
		{
			if (property == null)
			{
				metadataUnreliable = true;
				return null;
			}
			try
			{
				return property.GetValue(instance);
			}
			catch
			{
				metadataUnreliable = true;
				GuaranteeApparatusPlugin.Verbose("LLL reflection failed while reading '" + propertyName + "'.");
				return null;
			}
		}
	}
	[BepInPlugin("squirt.guaranteeapparatus", "GuaranteeApparatus", "1.0.0")]
	public sealed class GuaranteeApparatusPlugin : BaseUnityPlugin
	{
		internal const string PluginGuid = "squirt.guaranteeapparatus";

		internal const string PluginName = "GuaranteeApparatus";

		internal const string PluginVersion = "1.0.0";

		internal static ManualLogSource Log;

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<bool> VerboseLogging;

		internal static ConfigEntry<bool> RespectCustomApparatus;

		internal static ConfigEntry<bool> RespectUnknownCustomApparatus;

		internal static ConfigEntry<bool> SpawnFallbackWhenUnknownCustomHasNoDetectedApparatus;

		internal static ConfigEntry<bool> GenerateScanNodesForDetectedCustomApparatus;

		internal static ConfigEntry<bool> ForceVanillaFallbackOnKnownCustomInteriors;

		internal static ConfigEntry<bool> EnableVanillaSetpieceFallback;

		internal static ConfigEntry<float> MinimumEntranceDistance;

		internal static ConfigEntry<string> FactoryFlowPatterns;

		private Harmony? _harmony;

		internal static bool HasSpawnedFallbackThisRound { get; set; }

		private void Awake()
		{
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_0163: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable GuaranteeApparatus.");
			VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "VerboseLogging", false, "Enable verbose logging for classification and validation details.");
			RespectCustomApparatus = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RespectCustomApparatus", true, "When enabled, known custom apparatus systems suppress fallback by default.");
			RespectUnknownCustomApparatus = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "RespectUnknownCustomApparatus", true, "When enabled, detected unknown custom apparatuses suppress fallback by default.");
			SpawnFallbackWhenUnknownCustomHasNoDetectedApparatus = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "SpawnFallbackWhenUnknownCustomHasNoDetectedApparatus", true, "Allow vanilla fallback inside unknown custom interiors when no plausible custom apparatus is detected.");
			GenerateScanNodesForDetectedCustomApparatus = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "GenerateScanNodesForDetectedCustomApparatus", true, "Generate scan-node support for detected custom apparatuses when they lack discoverability metadata.");
			ForceVanillaFallbackOnKnownCustomInteriors = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "ForceVanillaFallbackOnKnownCustomInteriors", false, "Force vanilla fallback inside known custom interiors when no valid vanilla apparatus is found.");
			EnableVanillaSetpieceFallback = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableVanillaSetpieceFallback", false, "Deprecated. The fallback now always prefers a strict vanilla LungMachine setpiece sourced from factory tile prefabs, so this setting is ignored.");
			MinimumEntranceDistance = ((BaseUnityPlugin)this).Config.Bind<float>("General", "MinimumEntranceDistance", 10f, "Minimum distance from the main dungeon entrance required for fallback placement.");
			FactoryFlowPatterns = ((BaseUnityPlugin)this).Config.Bind<string>("General", "FactoryFlowPatterns", "factory,facility,level1flow,level2flow", "Comma-separated flow name fragments treated as factory-like for diagnostics.");
			_harmony = new Harmony("squirt.guaranteeapparatus");
			_harmony.PatchAll(typeof(RoundManagerPatches));
			Log.LogInfo((object)"GuaranteeApparatus 1.0.0 loaded.");
		}

		internal static void Verbose(string message)
		{
			if (VerboseLogging.Value)
			{
				Log.LogInfo((object)message);
			}
		}
	}
	internal static class RoundManagerPatches
	{
		[HarmonyPatch(typeof(RoundManager), "GenerateNewFloor")]
		[HarmonyPrefix]
		private static void GenerateNewFloorPrefix(RoundManager __instance)
		{
			GuaranteeApparatusPlugin.HasSpawnedFallbackThisRound = false;
			SafeRun("GenerateNewFloorPrefix/ScanNodeSupportService.EnsureMessagingReady", ScanNodeSupportService.EnsureMessagingReady);
			SafeRun("GenerateNewFloorPrefix/VanillaApparatusSetpieceService.EnsureMessagingReady", VanillaApparatusSetpieceService.EnsureMessagingReady);
		}

		[HarmonyPatch(typeof(RoundManager), "GeneratedFloorPostProcessing")]
		[HarmonyPostfix]
		private static void GeneratedFloorPostProcessingPostfix(RoundManager __instance)
		{
			RoundManager __instance2 = __instance;
			SafeRun("GeneratedFloorPostProcessing/ScanNodeSupportService.EnsureMessagingReady", ScanNodeSupportService.EnsureMessagingReady);
			SafeRun("GeneratedFloorPostProcessing/ScanNodeSupportService.TryResolvePending", delegate
			{
				ScanNodeSupportService.TryResolvePending(__instance2);
			});
			SafeRun("GeneratedFloorPostProcessing/VanillaApparatusSetpieceService.EnsureMessagingReady", VanillaApparatusSetpieceService.EnsureMessagingReady);
			SafeRun("GeneratedFloorPostProcessing/VanillaApparatusSetpieceService.TryResolvePending", VanillaApparatusSetpieceService.TryResolvePending);
			SafeRun("GeneratedFloorPostProcessing/ApparatusValidator.Evaluate", delegate
			{
				ApparatusValidator.Evaluate(__instance2);
			});
		}

		private static void SafeRun(string operationName, Action action)
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				GuaranteeApparatusPlugin.Log.LogWarning((object)(operationName + " failed; continuing without that step. " + ex.GetType().Name + ": " + ex.Message));
			}
		}
	}
	internal sealed class GuaranteeApparatusScanNodeMarker : MonoBehaviour
	{
		public string RelativeDungeonPath = string.Empty;
	}
	internal sealed class GuaranteeApparatusPendingScanNodeResolver : MonoBehaviour
	{
		private void Update()
		{
			ScanNodeSupportService.TryResolvePendingForActiveRound();
		}
	}
	internal enum ScanNodeSupportOutcome
	{
		Failed,
		AlreadyPresent,
		Generated
	}
	internal readonly struct ScanNodeDescriptor
	{
		public bool UseNetworkObjectLookup { get; }

		public ulong NetworkObjectId { get; }

		public string RelativeDungeonPath { get; }

		public string HeaderText { get; }

		public string SubText { get; }

		public int MinRange { get; }

		public int MaxRange { get; }

		public bool RequiresLineOfSight { get; }

		public int NodeType { get; }

		public ScanNodeDescriptor(bool useNetworkObjectLookup, ulong networkObjectId, string relativeDungeonPath, string headerText, string subText, int minRange, int maxRange, bool requiresLineOfSight, int nodeType)
		{
			UseNetworkObjectLookup = useNetworkObjectLookup;
			NetworkObjectId = networkObjectId;
			RelativeDungeonPath = relativeDungeonPath;
			HeaderText = headerText;
			SubText = subText;
			MinRange = minRange;
			MaxRange = maxRange;
			RequiresLineOfSight = requiresLineOfSight;
			NodeType = nodeType;
		}
	}
	internal static class ScanNodeSupportService
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static HandleNamedMessageDelegate <0>__OnScanNodeMessageReceived;
		}

		private const string MessageName = "GuaranteeApparatus/ScanNode";

		private const string HelperObjectName = "GuaranteeApparatusScanNode";

		private const string ResolverObjectName = "GuaranteeApparatusPendingScanNodeResolver";

		private static readonly List<ScanNodeDescriptor> PendingDescriptors = new List<ScanNodeDescriptor>();

		private static NetworkManager? _registeredManager;

		private static bool _messageHandlerRegistered;

		private static GuaranteeApparatusPendingScanNodeResolver? _resolver;

		public static void EnsureMessagingReady()
		{
			//IL_007c: 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_0087: Expected O, but got Unknown
			EnsureResolver();
			NetworkManager singleton = NetworkManager.Singleton;
			if (!((Object)(object)singleton == (Object)null) && singleton.CustomMessagingManager != null && (_registeredManager != singleton || !_messageHandlerRegistered))
			{
				if ((Object)(object)_registeredManager != (Object)null && _messageHandlerRegistered && _registeredManager.CustomMessagingManager != null)
				{
					_registeredManager.CustomMessagingManager.UnregisterNamedMessageHandler("GuaranteeApparatus/ScanNode");
				}
				CustomMessagingManager customMessagingManager = singleton.CustomMessagingManager;
				object obj = <>O.<0>__OnScanNodeMessageReceived;
				if (obj == null)
				{
					HandleNamedMessageDelegate val = OnScanNodeMessageReceived;
					<>O.<0>__OnScanNodeMessageReceived = val;
					obj = (object)val;
				}
				customMessagingManager.RegisterNamedMessageHandler("GuaranteeApparatus/ScanNode", (HandleNamedMessageDelegate)obj);
				_registeredManager = singleton;
				_messageHandlerRegistered = true;
			}
		}

		public static void TryResolvePending(RoundManager roundManager)
		{
			if (PendingDescriptors.Count == 0)
			{
				return;
			}
			for (int num = PendingDescriptors.Count - 1; num >= 0; num--)
			{
				ScanNodeDescriptor descriptor = PendingDescriptors[num];
				if (TryApplyDescriptor(roundManager, descriptor, out var _))
				{
					PendingDescriptors.RemoveAt(num);
				}
			}
		}

		public static void TryResolvePendingForActiveRound()
		{
			if (PendingDescriptors.Count != 0)
			{
				RoundManager val = Object.FindObjectOfType<RoundManager>();
				if ((Object)(object)val != (Object)null)
				{
					TryResolvePending(val);
				}
			}
		}

		public static ScanNodeSupportOutcome EnsureCustomScanSupport(ValidationContext context, DetectedCustomApparatusCandidate candidate)
		{
			if (!GuaranteeApparatusPlugin.GenerateScanNodesForDetectedCustomApparatus.Value || (Object)(object)candidate.RootTransform == (Object)null || !context.IsInDungeonHierarchy(candidate.RootTransform))
			{
				return ScanNodeSupportOutcome.Failed;
			}
			ScanNodeDescriptor descriptor = BuildCustomDescriptor(candidate);
			return EnsureDescriptor(candidate.RootTransform, descriptor);
		}

		public static ScanNodeSupportOutcome EnsureFallbackScanSupport(LungProp lungProp, string relativeDungeonPath)
		{
			NetworkObject val = (((Object)(object)lungProp != (Object)null) ? ((Component)lungProp).GetComponent<NetworkObject>() : null);
			if ((Object)(object)lungProp == (Object)null || (Object)(object)val == (Object)null || !val.IsSpawned)
			{
				return ScanNodeSupportOutcome.Failed;
			}
			ScanNodeDescriptor descriptor = BuildFallbackDescriptor(lungProp, val.NetworkObjectId, relativeDungeonPath);
			return EnsureFallbackDescriptor(((Component)lungProp).transform, descriptor);
		}

		private static void OnScanNodeMessageReceived(ulong senderClientId, FastBufferReader reader)
		{
			//IL_002a: 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_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: 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_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			bool useNetworkObjectLookup = false;
			ulong networkObjectId = 0uL;
			string empty = string.Empty;
			string empty2 = string.Empty;
			string empty3 = string.Empty;
			int minRange = 0;
			int maxRange = 0;
			bool requiresLineOfSight = false;
			int nodeType = 0;
			((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref useNetworkObjectLookup, default(ForPrimitives));
			((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
			((FastBufferReader)(ref reader)).ReadValueSafe(ref empty, true);
			((FastBufferReader)(ref reader)).ReadValueSafe(ref empty2, true);
			((FastBufferReader)(ref reader)).ReadValueSafe(ref empty3, true);
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref minRange, default(ForPrimitives));
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref maxRange, default(ForPrimitives));
			((FastBufferReader)(ref reader)).ReadValueSafe<bool>(ref requiresLineOfSight, default(ForPrimitives));
			((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref nodeType, default(ForPrimitives));
			ScanNodeDescriptor scanNodeDescriptor = new ScanNodeDescriptor(useNetworkObjectLookup, networkObjectId, empty, empty2, empty3, minRange, maxRange, requiresLineOfSight, nodeType);
			if (!TryApplyDescriptor(Object.FindObjectOfType<RoundManager>(), scanNodeDescriptor, out var _))
			{
				PendingDescriptors.Add(scanNodeDescriptor);
			}
		}

		private static void SendDescriptorToAll(ScanNodeDescriptor descriptor)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: 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)
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsServer || singleton.CustomMessagingManager == null)
			{
				return;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(2048, (Allocator)2, 2048);
			try
			{
				bool useNetworkObjectLookup = descriptor.UseNetworkObjectLookup;
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref useNetworkObjectLookup, default(ForPrimitives));
				ulong networkObjectId = descriptor.NetworkObjectId;
				((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
				((FastBufferWriter)(ref val)).WriteValueSafe(descriptor.RelativeDungeonPath, true);
				((FastBufferWriter)(ref val)).WriteValueSafe(descriptor.HeaderText, true);
				((FastBufferWriter)(ref val)).WriteValueSafe(descriptor.SubText, true);
				int minRange = descriptor.MinRange;
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref minRange, default(ForPrimitives));
				minRange = descriptor.MaxRange;
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref minRange, default(ForPrimitives));
				useNetworkObjectLookup = descriptor.RequiresLineOfSight;
				((FastBufferWriter)(ref val)).WriteValueSafe<bool>(ref useNetworkObjectLookup, default(ForPrimitives));
				minRange = descriptor.NodeType;
				((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref minRange, default(ForPrimitives));
				singleton.CustomMessagingManager.SendNamedMessageToAll("GuaranteeApparatus/ScanNode", val, (NetworkDelivery)2);
			}
			finally
			{
				((IDisposable)(FastBufferWriter)(ref val)).Dispose();
			}
		}

		private static ScanNodeDescriptor BuildCustomDescriptor(DetectedCustomApparatusCandidate candidate)
		{
			string headerText = (string.IsNullOrWhiteSpace(candidate.DisplayName) ? "Custom Apparatus" : candidate.DisplayName);
			string subText = ((candidate.Classification == InteriorClassification.KnownCustom) ? "Known Custom Apparatus" : "Unknown Custom Apparatus");
			return new ScanNodeDescriptor(useNetworkObjectLookup: false, 0uL, candidate.RelativeDungeonPath, headerText, subText, 0, 30, requiresLineOfSight: false, 0);
		}

		private static ScanNodeDescriptor BuildFallbackDescriptor(LungProp lungProp, ulong networkObjectId, string relativeDungeonPath)
		{
			string headerText = (string.IsNullOrWhiteSpace(((GrabbableObject)lungProp).itemProperties?.itemName) ? "Apparatus" : ((GrabbableObject)lungProp).itemProperties.itemName);
			return new ScanNodeDescriptor(useNetworkObjectLookup: true, networkObjectId, relativeDungeonPath, headerText, "Fallback Vanilla Apparatus", 0, 30, requiresLineOfSight: false, 0);
		}

		private static ScanNodeSupportOutcome EnsureDescriptor(Transform localTargetRoot, ScanNodeDescriptor descriptor)
		{
			if (HasExistingScanSupport(localTargetRoot))
			{
				return ScanNodeSupportOutcome.AlreadyPresent;
			}
			if (!TryApplyDescriptorToResolvedTarget(localTargetRoot, descriptor, out var generatedScanNode) || !generatedScanNode)
			{
				return ScanNodeSupportOutcome.Failed;
			}
			EnsureMessagingReady();
			SendDescriptorToAll(descriptor);
			return ScanNodeSupportOutcome.Generated;
		}

		private static ScanNodeSupportOutcome EnsureFallbackDescriptor(Transform localTargetRoot, ScanNodeDescriptor descriptor)
		{
			Transform val = SelectAttachmentTransform(localTargetRoot);
			bool num = (Object)(object)((Component)val).GetComponent<GuaranteeApparatusScanNodeMarker>() != (Object)null;
			EnsureMarker(((Component)val).gameObject, descriptor.RelativeDungeonPath);
			ApplyScanNodeComponent(((Component)val).gameObject, descriptor);
			if ((Object)(object)((Component)val).GetComponent<Collider>() == (Object)null && (Object)(object)((Component)val).GetComponent<Renderer>() == (Object)null)
			{
				EnsureHelperCollider(((Component)val).gameObject, localTargetRoot);
			}
			EnsureMessagingReady();
			SendDescriptorToAll(descriptor);
			if (!num)
			{
				return ScanNodeSupportOutcome.Generated;
			}
			return ScanNodeSupportOutcome.AlreadyPresent;
		}

		private static bool TryApplyDescriptor(RoundManager? roundManager, ScanNodeDescriptor descriptor, out bool generatedScanNode)
		{
			generatedScanNode = false;
			if (!TryResolveTargetTransform(roundManager, descriptor, out Transform targetRoot) || (Object)(object)targetRoot == (Object)null)
			{
				return false;
			}
			if (descriptor.UseNetworkObjectLookup)
			{
				Transform val = SelectAttachmentTransform(targetRoot);
				bool flag = (Object)(object)((Component)val).GetComponent<GuaranteeApparatusScanNodeMarker>() != (Object)null;
				EnsureMarker(((Component)val).gameObject, descriptor.RelativeDungeonPath);
				ApplyScanNodeComponent(((Component)val).gameObject, descriptor);
				if ((Object)(object)((Component)val).GetComponent<Collider>() == (Object)null && (Object)(object)((Component)val).GetComponent<Renderer>() == (Object)null)
				{
					EnsureHelperCollider(((Component)val).gameObject, targetRoot);
				}
				generatedScanNode = !flag;
				return true;
			}
			return TryApplyDescriptorToResolvedTarget(targetRoot, descriptor, out generatedScanNode);
		}

		private static bool TryResolveTargetTransform(RoundManager? roundManager, ScanNodeDescriptor descriptor, out Transform? targetRoot)
		{
			if (descriptor.UseNetworkObjectLookup)
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if ((Object)(object)singleton != (Object)null && singleton.SpawnManager != null && singleton.SpawnManager.SpawnedObjects.TryGetValue(descriptor.NetworkObjectId, out var value) && (Object)(object)value != (Object)null)
				{
					targetRoot = ((Component)value).transform;
					return true;
				}
			}
			if ((Object)(object)roundManager == (Object)null || (Object)(object)roundManager.dungeonGenerator == (Object)null || roundManager.dungeonGenerator.Generator == null)
			{
				targetRoot = null;
				return false;
			}
			Dungeon currentDungeon = roundManager.dungeonGenerator.Generator.CurrentDungeon;
			if ((Object)(object)currentDungeon == (Object)null)
			{
				targetRoot = null;
				return false;
			}
			return TryFindByRelativePath(((Component)currentDungeon).transform, descriptor.RelativeDungeonPath, out targetRoot);
		}

		private static bool TryApplyDescriptorToResolvedTarget(Transform targetRoot, ScanNodeDescriptor descriptor, out bool generatedScanNode)
		{
			generatedScanNode = false;
			if (HasExistingScanSupport(targetRoot))
			{
				return true;
			}
			Transform val = SelectAttachmentTransform(targetRoot);
			if (CanAttachDirectly(val))
			{
				EnsureMarker(((Component)val).gameObject, descriptor.RelativeDungeonPath);
				ApplyScanNodeComponent(((Component)val).gameObject, descriptor);
				generatedScanNode = true;
				return true;
			}
			GameObject orCreateHelperObject = GetOrCreateHelperObject(targetRoot, descriptor.RelativeDungeonPath);
			ApplyScanNodeComponent(orCreateHelperObject, descriptor);
			EnsureHelperCollider(orCreateHelperObject, targetRoot);
			generatedScanNode = true;
			return true;
		}

		private static bool HasExistingScanSupport(Transform rootTransform)
		{
			//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)
			if (((Component)rootTransform).GetComponentsInChildren<GuaranteeApparatusScanNodeMarker>(true).Length != 0)
			{
				return true;
			}
			ScanNodeProperties[] componentsInChildren = ((Component)rootTransform).GetComponentsInChildren<ScanNodeProperties>(true);
			foreach (ScanNodeProperties val in componentsInChildren)
			{
				if ((Object)(object)val != (Object)null)
				{
					Scene scene = ((Component)val).gameObject.scene;
					if (((Scene)(ref scene)).IsValid() && ((Behaviour)val).isActiveAndEnabled)
					{
						return true;
					}
				}
			}
			return false;
		}

		private static void EnsureResolver()
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			if (!((Object)(object)_resolver != (Object)null))
			{
				GuaranteeApparatusPendingScanNodeResolver guaranteeApparatusPendingScanNodeResolver = Object.FindObjectOfType<GuaranteeApparatusPendingScanNodeResolver>();
				if ((Object)(object)guaranteeApparatusPendingScanNodeResolver != (Object)null)
				{
					_resolver = guaranteeApparatusPendingScanNodeResolver;
					return;
				}
				GameObject val = new GameObject("GuaranteeApparatusPendingScanNodeResolver");
				Object.DontDestroyOnLoad((Object)val);
				_resolver = val.AddComponent<GuaranteeApparatusPendingScanNodeResolver>();
			}
		}

		private static Transform SelectAttachmentTransform(Transform targetRoot)
		{
			Transform targetRoot2 = targetRoot;
			Transform[] componentsInChildren = ((Component)targetRoot2).GetComponentsInChildren<Transform>(true);
			Array.Sort(componentsInChildren, (Transform left, Transform right) => string.Compare(GetRelativePath(left, targetRoot2), GetRelativePath(right, targetRoot2), StringComparison.Ordinal));
			Transform val = null;
			Transform val2 = null;
			GrabbableObject val4 = default(GrabbableObject);
			InteractTrigger val5 = default(InteractTrigger);
			TerminalAccessibleObject val6 = default(TerminalAccessibleObject);
			foreach (Transform val3 in componentsInChildren)
			{
				if (((Component)val3).TryGetComponent<GrabbableObject>(ref val4) || ((Component)val3).TryGetComponent<InteractTrigger>(ref val5) || ((Component)val3).TryGetComponent<TerminalAccessibleObject>(ref val6))
				{
					return val3;
				}
				if ((Object)(object)val == (Object)null && (Object)(object)((Component)val3).GetComponent<Collider>() != (Object)null)
				{
					val = val3;
				}
				if ((Object)(object)val2 == (Object)null && (Object)(object)((Component)val3).GetComponent<Renderer>() != (Object)null)
				{
					val2 = val3;
				}
			}
			return val ?? val2 ?? targetRoot2;
		}

		private static bool CanAttachDirectly(Transform attachmentTransform)
		{
			GrabbableObject val = default(GrabbableObject);
			InteractTrigger val2 = default(InteractTrigger);
			TerminalAccessibleObject val3 = default(TerminalAccessibleObject);
			if (!((Component)attachmentTransform).TryGetComponent<GrabbableObject>(ref val) && !((Component)attachmentTransform).TryGetComponent<InteractTrigger>(ref val2) && !((Component)attachmentTransform).TryGetComponent<TerminalAccessibleObject>(ref val3) && !((Object)(object)((Component)attachmentTransform).GetComponent<Collider>() != (Object)null))
			{
				return (Object)(object)((Component)attachmentTransform).GetComponent<Renderer>() != (Object)null;
			}
			return true;
		}

		private static void EnsureMarker(GameObject gameObject, string relativeDungeonPath)
		{
			(gameObject.GetComponent<GuaranteeApparatusScanNodeMarker>() ?? gameObject.AddComponent<GuaranteeApparatusScanNodeMarker>()).RelativeDungeonPath = relativeDungeonPath;
		}

		private static void ApplyScanNodeComponent(GameObject gameObject, ScanNodeDescriptor descriptor)
		{
			ScanNodeProperties obj = gameObject.GetComponent<ScanNodeProperties>() ?? gameObject.AddComponent<ScanNodeProperties>();
			obj.headerText = descriptor.HeaderText;
			obj.subText = descriptor.SubText;
			obj.minRange = descriptor.MinRange;
			obj.maxRange = descriptor.MaxRange;
			obj.requiresLineOfSight = descriptor.RequiresLineOfSight;
			obj.nodeType = descriptor.NodeType;
			obj.scrapValue = 0;
			obj.creatureScanID = -1;
		}

		private static GameObject GetOrCreateHelperObject(Transform targetRoot, string relativeDungeonPath)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			//IL_0099: Expected O, but got Unknown
			GuaranteeApparatusScanNodeMarker[] componentsInChildren = ((Component)targetRoot).GetComponentsInChildren<GuaranteeApparatusScanNodeMarker>(true);
			foreach (GuaranteeApparatusScanNodeMarker guaranteeApparatusScanNodeMarker in componentsInChildren)
			{
				if (!((Object)(object)guaranteeApparatusScanNodeMarker == (Object)null) && string.Equals(guaranteeApparatusScanNodeMarker.RelativeDungeonPath, relativeDungeonPath, StringComparison.Ordinal))
				{
					return ((Component)guaranteeApparatusScanNodeMarker).gameObject;
				}
			}
			GameObject val = new GameObject("GuaranteeApparatusScanNode");
			val.transform.SetParent(targetRoot, false);
			val.transform.localPosition = Vector3.zero;
			val.transform.localRotation = Quaternion.identity;
			val.transform.localScale = Vector3.one;
			val.layer = ((Component)targetRoot).gameObject.layer;
			EnsureMarker(val, relativeDungeonPath);
			return val;
		}

		private static void EnsureHelperCollider(GameObject helperObject, Transform targetRoot)
		{
			SphereCollider obj = helperObject.GetComponent<SphereCollider>() ?? helperObject.AddComponent<SphereCollider>();
			((Collider)obj).isTrigger = true;
			obj.radius = 0.35f;
			helperObject.layer = ((Component)targetRoot).gameObject.layer;
		}

		private static string GetRelativePath(Transform transform, Transform rootTransform)
		{
			if ((Object)(object)transform == (Object)(object)rootTransform)
			{
				return string.Empty;
			}
			Stack<string> stack = new Stack<string>();
			Transform val = transform;
			while ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)rootTransform)
			{
				stack.Push(((Object)val).name);
				val = val.parent;
			}
			return string.Join("/", stack);
		}

		private static bool TryFindByRelativePath(Transform rootTransform, string relativePath, out Transform? foundTransform)
		{
			if (string.IsNullOrWhiteSpace(relativePath))
			{
				foundTransform = null;
				return false;
			}
			string[] array = relativePath.Split('/');
			Transform val = rootTransform;
			foreach (string b in array)
			{
				Transform val2 = null;
				int childCount = val.childCount;
				for (int j = 0; j < childCount; j++)
				{
					Transform child = val.GetChild(j);
					if (string.Equals(((Object)child).name, b, StringComparison.Ordinal))
					{
						val2 = child;
						break;
					}
				}
				if ((Object)(object)val2 == (Object)null)
				{
					foundTransform = null;
					return false;
				}
				val = val2;
			}
			foundTransform = val;
			return (Object)(object)foundTransform != (Object)null;
		}
	}
	internal sealed class DetectedCustomApparatusCandidate
	{
		public InteriorClassification Classification { get; }

		public string SourceId { get; }

		public string SourceDisplayName { get; }

		public Transform RootTransform { get; }

		public string RelativeDungeonPath { get; }

		public string DisplayName { get; }

		public string Reason { get; }

		public int Score { get; }

		public DetectedCustomApparatusCandidate(InteriorClassification classification, string sourceId, string sourceDisplayName, Transform rootTransform, string relativeDungeonPath, string displayName, string reason, int score)
		{
			Classification = classification;
			SourceId = sourceId;
			SourceDisplayName = sourceDisplayName;
			RootTransform = rootTransform;
			RelativeDungeonPath = relativeDungeonPath;
			DisplayName = displayName;
			Reason = reason;
			Score = score;
		}

		public bool ControlsTransform(Transform transform)
		{
			if (!((Object)(object)transform == (Object)(object)RootTransform))
			{
				return transform.IsChildOf(RootTransform);
			}
			return true;
		}
	}
	internal readonly struct SignalEvidence
	{
		public string Key { get; }

		public string Reason { get; }

		public SignalEvidence(string key, string reason)
		{
			Key = key;
			Reason = reason;
		}
	}
	internal readonly struct RootCandidateGroup
	{
		public Transform RootTransform { get; }

		public string RelativeDungeonPath { get; }

		public RootCandidateGroup(Transform rootTransform, string relativeDungeonPath)
		{
			RootTransform = rootTransform;
			RelativeDungeonPath = relativeDungeonPath;
		}
	}
	internal static class UnknownCustomApparatusDiscovery
	{
		private static readonly string[] PrimaryTokens = new string[7] { "apparatus", "lung", "generator", "socket", "upturned", "power", "core" };

		private static readonly string[] PowerTokens = new string[11]
		{
			"power", "generator", "socket", "breaker", "battery", "reactor", "charge", "switch", "fuse", "core",
			"elevator"
		};

		private static readonly string[] VariantTokens = new string[6] { "off", "disabled", "inactive", "turnedoff", "turned_off", "turned-off" };

		private const int MaxGroupedRootsPerScan = 100;

		public static bool TryDiscover(ValidationContext context, out DetectedCustomApparatusCandidate? detectedCandidate)
		{
			List<RootCandidateGroup> list = BuildRootCandidateGroups(context);
			for (int i = 0; i < list.Count; i++)
			{
				if (TryEvaluateGroup(context, list[i], out DetectedCustomApparatusCandidate detectedCandidate2))
				{
					DetectedCustomApparatusCandidate detectedCustomApparatusCandidate = (detectedCandidate = detectedCandidate2);
					Guara