Decompiled source of FriendlyGabriel v2.0.12

FriendlyGabriel.dll

Decompiled 2 hours 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 System.Text.RegularExpressions;
using BepInEx;
using BepInEx.Logging;
using Configgy;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("FriendlyGabriel")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("2.0.12.0")]
[assembly: AssemblyInformationalVersion("2.0.12")]
[assembly: AssemblyProduct("FriendlyGabriel")]
[assembly: AssemblyTitle("FriendlyGabriel")]
[assembly: AssemblyVersion("2.0.12.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FriendlyGabriel
{
	internal static class Cfg
	{
		[Configgable("General", "Enable Friendly Gabriel", 0, null)]
		public static bool Enabled = true;

		[Configgable("General", "Debug logging", 0, null)]
		public static bool DebugLogging = false;

		[Configgable("Companions", "Gabriel, Judge of Hell is friendly", 0, null)]
		public static bool FirstGabriel = true;

		[Configgable("Companions", "Gabriel, Apostate of Hope is friendly", 0, null)]
		public static bool SecondGabriel = true;

		[Configgable("Durability", "Immortal", 0, null)]
		public static bool Immortal = true;

		[Configgable("Durability", "Health pool", 0, null)]
		public static float MaxHealth = 999f;

		[Configgable("Durability", "Revive if killed anyway", 0, null)]
		public static bool ReviveIfKilled = true;

		[Configgable("Following", "Follow player", 0, null)]
		public static bool FollowPlayer = true;

		[Configgable("Following", "Stay within distance", 0, null)]
		public static float FollowDistance = 5f;

		[Configgable("Following", "Follow speed", 0, null)]
		public static float FollowSpeed = 12f;

		[Configgable("Following", "Face the player while following", 0, null)]
		public static bool FacePlayer = true;

		[Configgable("Following", "Teleport distance", 0, null)]
		public static float TeleportDistance = 80f;

		[Configgable("Spawning", "Spawn companion Gabriel", 0, null)]
		public static bool SpawnCompanion = true;

		[Configgable("Spawning", "Spawn after level", 0, null)]
		public static string SpawnAfterLevel = "3-2";

		[Configgable("Spawning", "Spawn Gabriel Second instead", 0, null)]
		public static bool SpawnGabrielSecond = false;

		[Configgable("Spawning", "Spawn distance", 0, null)]
		public static float SpawnDistance = 4f;

		[Configgable("Spawning", "Prefab keys", 0, null)]
		public static string PrefabKeys = "Assets/Prefabs/Enemies/Gabriel.prefab,Assets/Prefabs/Enemies/Gabriel Second.prefab";

		[Configgable("Protection", "Block damage to me near friendly Gabriel", 0, null)]
		public static bool ProtectPlayer = true;

		[Configgable("Protection", "Protection radius", 0, null)]
		public static float ProtectRadius = 30f;

		[Configgable("Combat", "Attack enemies", 0, null)]
		public static bool AttackEnemies = true;

		[Configgable("Combat", "Max target range", 0, null)]
		public static float TargetRange = 0f;

		[Configgable("Combat", "Enemy scan interval", 0, null)]
		public static float ScanInterval = 0.5f;

		[Configgable("Advanced", "Ignored enemy types", 0, null)]
		public static string IgnoredEnemyTypes = "None";

		[Configgable("Advanced", "Decoy target when no enemies left", 0, null)]
		public static bool UseDecoyTarget = true;

		[Configgable("Level Progression", "Open exit without killing Gabriel", 0, null)]
		public static bool OpenExitWithoutKill = true;

		[Configgable("Level Progression", "Exit delay", 0, null)]
		public static float OpenExitDelay = 3f;

		[Configgable("Level Progression", "Exit levels", 0, null)]
		public static string ExitLevels = "3-2,6-2";

		private static string _lastIgnoredRaw;

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

		public static bool IsIgnored(EnemyType type)
		{
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Expected I4, but got Unknown
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: Expected I4, but got Unknown
			string text = IgnoredEnemyTypes ?? string.Empty;
			if (!string.Equals(text, _lastIgnoredRaw, StringComparison.Ordinal))
			{
				_lastIgnoredRaw = text;
				_ignored.Clear();
				string[] array = text.Split(',');
				foreach (string text2 in array)
				{
					string text3 = text2.Trim();
					if (text3.Length != 0 && !text3.Equals("None", StringComparison.OrdinalIgnoreCase))
					{
						if (Enum.TryParse<EnemyType>(text3, ignoreCase: true, out EnemyType result))
						{
							_ignored.Add((int)result);
						}
						else
						{
							FriendlyGabrielPlugin.Log.LogWarning((object)("Unknown enemy type in ignore list: " + text3));
						}
					}
				}
			}
			return _ignored.Contains((int)type);
		}
	}
	internal static class CompanionManager
	{
		private class TargetCache
		{
			public Transform Victim;

			public EnemyTarget VictimTarget;

			public GameObject Decoy;

			public bool FallbackSet;

			public EnemyTarget DecoyTarget;
		}

		private class Seen
		{
			public Transform T;

			public float Time;
		}

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

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

		private static readonly Dictionary<int, TargetCache> _cache = new Dictionary<int, TargetCache>();

		private static EnemyIdentifier[] _enemies = (EnemyIdentifier[])(object)new EnemyIdentifier[0];

		private static float _nextScan;

		private static Transform _player;

		private const BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

		private static bool _nativeLooked;

		private static FieldInfo _fIgnorePlayer;

		private static FieldInfo _fAttackEnemies;

		private static MethodInfo _mSetFallbackTarget;

		private static PropertyInfo _pIsCurrentTargetFallback;

		private static bool _fallbackBroken;

		private static readonly Dictionary<int, Seen> _seen = new Dictionary<int, Seen>();

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

		private static readonly Dictionary<int, EnemyIdentifier> _companions = new Dictionary<int, EnemyIdentifier>();

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

		private static Gabriel[] _gabriels = (Gabriel[])(object)new Gabriel[0];

		private static GabrielSecond[] _gabrielSeconds = (GabrielSecond[])(object)new GabrielSecond[0];

		public static bool LoopDrivesTick = true;

		private static readonly Dictionary<int, EnemyIdentifier> _hookEid = new Dictionary<int, EnemyIdentifier>();

		private static bool _hookLogged;

		private static bool _loopLogged;

		private static bool _scanLoggedThisScene;

		private static bool Native
		{
			get
			{
				if (!_nativeLooked)
				{
					_nativeLooked = true;
					Type typeFromHandle = typeof(EnemyIdentifier);
					_fIgnorePlayer = typeFromHandle.GetField("ignorePlayer", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					_fAttackEnemies = typeFromHandle.GetField("attackEnemies", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					_mSetFallbackTarget = typeFromHandle.GetMethod("SetFallbackTarget", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					_pIsCurrentTargetFallback = typeFromHandle.GetProperty("IsCurrentTargetFallback", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					FriendlyGabrielPlugin.Log.LogInfo((object)("Game friendly-AI fields: ignorePlayer=" + (_fIgnorePlayer != null) + ", attackEnemies=" + (_fAttackEnemies != null) + ", SetFallbackTarget=" + (_mSetFallbackTarget != null) + ", IsCurrentTargetFallback=" + (_pIsCurrentTargetFallback != null)));
				}
				return _fIgnorePlayer != null && _fAttackEnemies != null;
			}
		}

		public static bool FirstIsImmortal => Cfg.Enabled && Cfg.FirstGabriel && Cfg.Immortal;

		public static bool SecondIsImmortal => Cfg.Enabled && Cfg.SecondGabriel && Cfg.Immortal;

		private unsafe static int Kind(EnemyType t)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Expected I4, but got Unknown
			int num = (int)t;
			if (!_kindCache.TryGetValue(num, out var value))
			{
				string text = ((object)(*(EnemyType*)(&t))/*cast due to .constrained prefix*/).ToString();
				value = num switch
				{
					28 => 2, 
					16 => 1, 
					_ => (text == "Gabriel") ? 1 : ((text == "GabrielSecond") ? 2 : 0), 
				};
				_kindCache[num] = value;
			}
			return value;
		}

		private static void ApplyNative(EnemyIdentifier eid)
		{
			_fIgnorePlayer.SetValue(eid, true);
			_fAttackEnemies.SetValue(eid, Cfg.AttackEnemies);
		}

		private static bool CurrentTargetIsFallback(EnemyIdentifier eid)
		{
			if (_pIsCurrentTargetFallback == null)
			{
				return true;
			}
			try
			{
				return (bool)_pIsCurrentTargetFallback.GetValue(eid, null);
			}
			catch
			{
				return true;
			}
		}

		private static void SetFallback(EnemyIdentifier eid, TargetCache cache, Transform self, Transform player)
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: 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_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: 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_0081: 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_00b9: 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_00c9: 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_00ac: Unknown result type (might be due to invalid IL or missing references)
			if (!Cfg.UseDecoyTarget || _mSetFallbackTarget == null || _fallbackBroken)
			{
				return;
			}
			if ((Object)(object)cache.Decoy == (Object)null)
			{
				cache.Decoy = new GameObject("FriendlyGabriel_Decoy");
			}
			Vector3 val = (((Object)(object)player != (Object)null) ? (self.position - player.position) : self.forward);
			((Vector3)(ref val))..ctor(val.x, 0f, val.z);
			if (Vector3.Distance(val, new Vector3(0f, 0f, 0f)) < 0.01f)
			{
				val = self.forward;
			}
			cache.Decoy.transform.position = self.position + val * 40f;
			if (cache.FallbackSet)
			{
				return;
			}
			try
			{
				_mSetFallbackTarget.Invoke(eid, new object[1] { cache.Decoy.transform });
				cache.FallbackSet = true;
				FriendlyGabrielPlugin.Log.LogInfo((object)("Fallback target set for " + ((object)Unsafe.As<EnemyType, EnemyType>(ref eid.enemyType)/*cast due to .constrained prefix*/).ToString()));
			}
			catch (Exception ex)
			{
				_fallbackBroken = true;
				FriendlyGabrielPlugin.Log.LogWarning((object)("SetFallbackTarget failed, decoy disabled: " + ex.Message));
			}
		}

		private static void MarkSeen(EnemyIdentifier eid)
		{
			int instanceID = ((Object)eid).GetInstanceID();
			if (!_seen.TryGetValue(instanceID, out var value))
			{
				value = new Seen();
				_seen[instanceID] = value;
			}
			value.T = ((Component)eid).transform;
			value.Time = Time.time;
		}

		public static bool PlayerShouldBeProtected()
		{
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			if (!Cfg.Enabled || !Cfg.ProtectPlayer)
			{
				return false;
			}
			Transform player = GetPlayer();
			if ((Object)(object)player == (Object)null)
			{
				return false;
			}
			float num = Mathf.Max(1f, Cfg.ProtectRadius);
			foreach (Seen value in _seen.Values)
			{
				if ((Object)(object)value.T == (Object)null || Time.time - value.Time > 1f || !(Vector3.Distance(value.T.position, player.position) <= num))
				{
					continue;
				}
				return true;
			}
			return false;
		}

		private static int KindOf(EnemyIdentifier eid)
		{
			//IL_0021: 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_00b2: Expected I4, but got Unknown
			int instanceID = ((Object)eid).GetInstanceID();
			if (_instanceKind.TryGetValue(instanceID, out var value))
			{
				return value;
			}
			value = Kind(eid.enemyType);
			if (value == 0)
			{
				if ((Object)(object)((Component)eid).GetComponent<Gabriel>() != (Object)null)
				{
					value = 1;
				}
				else if ((Object)(object)((Component)eid).GetComponent<GabrielSecond>() != (Object)null)
				{
					value = 2;
				}
			}
			_instanceKind[instanceID] = value;
			if (value != 0)
			{
				FriendlyGabrielPlugin.Log.LogInfo((object)("Gabriel recognised: enemy type " + ((object)Unsafe.As<EnemyType, EnemyType>(ref eid.enemyType)/*cast due to .constrained prefix*/).ToString() + " (" + (int)eid.enemyType + "), kind " + value));
			}
			return value;
		}

		public static bool IsCompanion(EnemyIdentifier eid)
		{
			if (!Cfg.Enabled || (Object)(object)eid == (Object)null)
			{
				return false;
			}
			int num = KindOf(eid);
			return (num == 1 && Cfg.FirstGabriel) || (num == 2 && Cfg.SecondGabriel);
		}

		public static void OnSceneLoaded()
		{
			LevelProgression.OnSceneLoaded();
			SpawnManager.OnSceneLoaded();
			_scanLoggedThisScene = false;
			_lastTickFrame.Clear();
			_companions.Clear();
			_hookEid.Clear();
			_instanceKind.Clear();
			_gabriels = (Gabriel[])(object)new Gabriel[0];
			_gabrielSeconds = (GabrielSecond[])(object)new GabrielSecond[0];
			_seen.Clear();
			_player = null;
			_enemies = (EnemyIdentifier[])(object)new EnemyIdentifier[0];
			_nextScan = 0f;
			foreach (TargetCache value in _cache.Values)
			{
				if ((Object)(object)value.Decoy != (Object)null)
				{
					Object.Destroy((Object)(object)value.Decoy);
				}
			}
			_cache.Clear();
		}

		public static Transform PlayerTransform()
		{
			return GetPlayer();
		}

		public static bool HasAnyGabriel()
		{
			RefreshEnemies();
			for (int i = 0; i < _gabriels.Length; i++)
			{
				if ((Object)(object)_gabriels[i] != (Object)null)
				{
					return true;
				}
			}
			for (int j = 0; j < _gabrielSeconds.Length; j++)
			{
				if ((Object)(object)_gabrielSeconds[j] != (Object)null)
				{
					return true;
				}
			}
			return false;
		}

		public static void MoveAll()
		{
			//IL_008b: 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_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: 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)
			if (!Cfg.Enabled || !Native)
			{
				return;
			}
			Transform player = GetPlayer();
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			foreach (EnemyIdentifier value in _companions.Values)
			{
				if (!((Object)(object)value == (Object)null) && IsCompanion(value))
				{
					Transform transform = ((Component)value).transform;
					if (Cfg.TeleportDistance > 0f && Vector3.Distance(transform.position, player.position) > Cfg.TeleportDistance)
					{
						transform.position = player.position - player.forward * Mathf.Max(2f, Cfg.FollowDistance);
					}
					if (Cfg.FollowPlayer && (Object)(object)FindNearestEnemy(value) == (Object)null)
					{
						FollowStep(transform, player);
					}
				}
			}
		}

		public static void Tick(EnemyIdentifier eid)
		{
			//IL_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: 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_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Expected O, but got Unknown
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_0289: Unknown result type (might be due to invalid IL or missing references)
			//IL_0293: Unknown result type (might be due to invalid IL or missing references)
			//IL_0298: Unknown result type (might be due to invalid IL or missing references)
			//IL_0253: Unknown result type (might be due to invalid IL or missing references)
			//IL_025d: Expected O, but got Unknown
			//IL_026b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0275: Expected O, but got Unknown
			if (!IsCompanion(eid))
			{
				return;
			}
			int instanceID = ((Object)eid).GetInstanceID();
			if (_lastTickFrame.TryGetValue(instanceID, out var value) && value == Time.frameCount)
			{
				return;
			}
			_lastTickFrame[instanceID] = Time.frameCount;
			MarkSeen(eid);
			_companions[((Object)eid).GetInstanceID()] = eid;
			LevelProgression.Tick(eid);
			DebugDump.Once(eid);
			if (Cfg.Immortal)
			{
				if (Cfg.ReviveIfKilled && eid.dead)
				{
					eid.dead = false;
				}
				float num = Mathf.Max(1f, Cfg.MaxHealth);
				if (eid.health < num)
				{
					eid.health = num;
				}
			}
			Transform player = GetPlayer();
			Transform transform = ((Component)eid).transform;
			if (!Native && (Object)(object)player != (Object)null && Cfg.TeleportDistance > 0f && Vector3.Distance(transform.position, player.position) > Cfg.TeleportDistance)
			{
				transform.position = player.position - player.forward * Mathf.Max(2f, Cfg.FollowDistance);
			}
			TargetCache cache = GetCache(eid);
			if (Native)
			{
				ApplyNative(eid);
				SetFallback(eid, cache, transform, player);
				return;
			}
			EnemyIdentifier val = (Cfg.AttackEnemies ? FindNearestEnemy(eid) : null);
			if ((Object)(object)val != (Object)null)
			{
				if ((Object)(object)cache.Victim != (Object)(object)((Component)val).transform)
				{
					cache.Victim = ((Component)val).transform;
					cache.VictimTarget = new EnemyTarget(((Component)val).transform);
					if (Cfg.DebugLogging)
					{
						FriendlyGabrielPlugin.Log.LogInfo((object)(((object)Unsafe.As<EnemyType, EnemyType>(ref eid.enemyType)/*cast due to .constrained prefix*/).ToString() + " now targeting " + ((object)Unsafe.As<EnemyType, EnemyType>(ref val.enemyType)/*cast due to .constrained prefix*/).ToString()));
					}
				}
				eid.target = cache.VictimTarget;
				return;
			}
			cache.Victim = null;
			if (Cfg.UseDecoyTarget)
			{
				if ((Object)(object)cache.Decoy == (Object)null)
				{
					cache.Decoy = new GameObject("FriendlyGabriel_Decoy");
					cache.DecoyTarget = new EnemyTarget(cache.Decoy.transform);
				}
				cache.Decoy.transform.position = transform.position + transform.forward * 30f;
				eid.target = cache.DecoyTarget;
			}
			else
			{
				eid.target = null;
			}
			if (Cfg.FollowPlayer && (Object)(object)player != (Object)null)
			{
				FollowStep(transform, player);
			}
		}

		private static void FollowStep(Transform self, Transform player)
		{
			//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_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_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			Vector3 position = player.position;
			float num = Vector3.Distance(self.position, position);
			if (!(num <= Mathf.Max(0.5f, Cfg.FollowDistance)))
			{
				self.position = Vector3.MoveTowards(self.position, position, Mathf.Max(0f, Cfg.FollowSpeed) * Time.deltaTime);
				if (Cfg.FacePlayer)
				{
					self.LookAt(new Vector3(position.x, self.position.y, position.z));
				}
			}
		}

		private static Transform GetPlayer()
		{
			if ((Object)(object)_player == (Object)null)
			{
				GameObject val = GameObject.FindWithTag("Player");
				_player = (((Object)(object)val != (Object)null) ? val.transform : null);
			}
			return _player;
		}

		private static TargetCache GetCache(EnemyIdentifier eid)
		{
			int instanceID = ((Object)eid).GetInstanceID();
			if (!_cache.TryGetValue(instanceID, out var value))
			{
				value = new TargetCache();
				_cache[instanceID] = value;
			}
			return value;
		}

		public static void TickFromHook(Component gabriel)
		{
			if ((Object)(object)gabriel == (Object)null || !Cfg.Enabled)
			{
				return;
			}
			int instanceID = ((Object)gabriel).GetInstanceID();
			if (!_hookEid.TryGetValue(instanceID, out var value))
			{
				value = gabriel.GetComponent<EnemyIdentifier>();
				_hookEid[instanceID] = value;
			}
			if (!((Object)(object)value == (Object)null))
			{
				if (!_hookLogged)
				{
					_hookLogged = true;
					FriendlyGabrielPlugin.Log.LogInfo((object)("Gabriel hook running on " + ((object)gabriel).GetType().Name + " (enemy type " + ((object)Unsafe.As<EnemyType, EnemyType>(ref value.enemyType)/*cast due to .constrained prefix*/).ToString() + ", companion=" + IsCompanion(value) + ")"));
				}
				if (IsCompanion(value))
				{
					Tick(value);
				}
			}
		}

		private static void RefreshEnemies()
		{
			if (Time.unscaledTime >= _nextScan)
			{
				_enemies = Object.FindObjectsOfType<EnemyIdentifier>();
				_gabriels = Object.FindObjectsOfType<Gabriel>();
				_gabrielSeconds = Object.FindObjectsOfType<GabrielSecond>();
				_nextScan = Time.unscaledTime + Mathf.Max(0.1f, Cfg.ScanInterval);
				if (!_scanLoggedThisScene && (_enemies.Length != 0 || _gabriels.Length != 0 || _gabrielSeconds.Length != 0))
				{
					_scanLoggedThisScene = true;
					FriendlyGabrielPlugin.Log.LogInfo((object)("Scan found " + _enemies.Length + " enemies, " + _gabriels.Length + " Gabriel and " + _gabrielSeconds.Length + " GabrielSecond components"));
				}
			}
		}

		public static void TickAll()
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Expected I4, but got Unknown
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Expected I4, but got Unknown
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			if (!_loopLogged)
			{
				_loopLogged = true;
				FriendlyGabrielPlugin.Log.LogInfo((object)("Companion loop running (Enabled=" + Cfg.Enabled + ")"));
			}
			if (!Cfg.Enabled || !LoopDrivesTick)
			{
				return;
			}
			RefreshEnemies();
			for (int i = 0; i < _enemies.Length; i++)
			{
				EnemyIdentifier val = _enemies[i];
				if (!((Object)(object)val == (Object)null))
				{
					if (_loggedTypes.Add((int)val.enemyType))
					{
						FriendlyGabrielPlugin.Log.LogInfo((object)("Enemy type seen: " + ((object)Unsafe.As<EnemyType, EnemyType>(ref val.enemyType)/*cast due to .constrained prefix*/).ToString() + " (" + (int)val.enemyType + ")" + ((Kind(val.enemyType) != 0) ? " <- treated as Gabriel" : "")));
					}
					if (IsCompanion(val))
					{
						Tick(val);
					}
				}
			}
			for (int j = 0; j < _gabriels.Length; j++)
			{
				TickFromComponent((Component)(object)_gabriels[j], 1);
			}
			for (int k = 0; k < _gabrielSeconds.Length; k++)
			{
				TickFromComponent((Component)(object)_gabrielSeconds[k], 2);
			}
		}

		private static void TickFromComponent(Component c, int kind)
		{
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Expected I4, but got Unknown
			if ((Object)(object)c == (Object)null)
			{
				return;
			}
			EnemyIdentifier component = c.GetComponent<EnemyIdentifier>();
			if (!((Object)(object)component == (Object)null))
			{
				int instanceID = ((Object)component).GetInstanceID();
				if (!_instanceKind.TryGetValue(instanceID, out var value) || value == 0)
				{
					_instanceKind[instanceID] = kind;
					FriendlyGabrielPlugin.Log.LogInfo((object)("Gabriel recognised from component: enemy type " + ((object)Unsafe.As<EnemyType, EnemyType>(ref component.enemyType)/*cast due to .constrained prefix*/).ToString() + " (" + (int)component.enemyType + "), kind " + kind));
				}
				if (IsCompanion(component))
				{
					Tick(component);
				}
			}
		}

		private static EnemyIdentifier FindNearestEnemy(EnemyIdentifier self)
		{
			//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_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: 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)
			RefreshEnemies();
			Vector3 position = ((Component)self).transform.position;
			float num = ((Cfg.TargetRange > 0f) ? Cfg.TargetRange : float.PositiveInfinity);
			EnemyIdentifier result = null;
			for (int i = 0; i < _enemies.Length; i++)
			{
				EnemyIdentifier val = _enemies[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)self) && !val.dead && !IsCompanion(val) && !Cfg.IsIgnored(val.enemyType))
				{
					float num2 = Vector3.Distance(position, ((Component)val).transform.position);
					if (num2 < num)
					{
						num = num2;
						result = val;
					}
				}
			}
			return result;
		}
	}
	internal static class DebugDump
	{
		private static readonly HashSet<string> _done = new HashSet<string>();

		public static void Once(EnemyIdentifier eid)
		{
			Dump(eid);
			Dump(((Component)eid).GetComponent<Gabriel>());
			Dump(((Component)eid).GetComponent<GabrielSecond>());
		}

		private static void Dump(object comp)
		{
			if (comp == null)
			{
				return;
			}
			Type type = comp.GetType();
			if (!_done.Add(type.Name))
			{
				return;
			}
			FriendlyGabrielPlugin.Log.LogInfo((object)("--- " + type.Name + " members of interest (base: " + ((type.BaseType != null) ? type.BaseType.Name : "none") + ") ---"));
			FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (FieldInfo fieldInfo in fields)
			{
				if (Interesting(fieldInfo.Name) || Interesting(fieldInfo.FieldType.Name))
				{
					FriendlyGabrielPlugin.Log.LogInfo((object)("field " + fieldInfo.FieldType.Name + " " + fieldInfo.Name));
				}
			}
			MethodInfo[] methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (Interesting(methodInfo.Name))
				{
					FriendlyGabrielPlugin.Log.LogInfo((object)("method " + methodInfo.Name + " (" + methodInfo.GetParameters().Length + " params)"));
				}
			}
		}

		private static bool Interesting(string n)
		{
			string text = n.ToLowerInvariant();
			return text.Contains("target") || text.Contains("player") || text.Contains("damage") || text.Contains("hurt") || text.Contains("ignore") || text.Contains("friend") || text.Contains("attack") || text.Contains("death");
		}
	}
	internal static class LevelId
	{
		private const BindingFlags Static = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly Regex LevelPattern = new Regex("(\\d+)-(\\d+)");

		private static Assembly Game => typeof(EnemyIdentifier).Assembly;

		public static string Best(out string candidates)
		{
			//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)
			string text = FromSceneHelper();
			int levelNumber;
			string text2 = FromStats(out levelNumber);
			Scene activeScene = SceneManager.GetActiveScene();
			string name = ((Scene)(ref activeScene)).name;
			candidates = "SceneHelper.CurrentScene='" + text + "', StatsManager mission='" + text2 + "' (levelNumber=" + levelNumber + "), scene='" + name + "'";
			string[] array = new string[3] { text, text2, name };
			foreach (string text3 in array)
			{
				if (!string.IsNullOrEmpty(text3) && LevelPattern.IsMatch(text3))
				{
					return text3;
				}
			}
			return text ?? text2 ?? name ?? "";
		}

		public static string Best()
		{
			string candidates;
			return Best(out candidates);
		}

		public static bool TryParse(string id, out int layer, out int number)
		{
			layer = (number = 0);
			Match match = LevelPattern.Match(id ?? "");
			if (!match.Success)
			{
				return false;
			}
			layer = int.Parse(match.Groups[1].Value);
			number = int.Parse(match.Groups[2].Value);
			return true;
		}

		private static string FromSceneHelper()
		{
			try
			{
				Type type = Game.GetType("SceneHelper");
				if (type == null)
				{
					return null;
				}
				PropertyInfo property = type.GetProperty("CurrentScene", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null)
				{
					return property.GetValue(null, null) as string;
				}
				FieldInfo field = type.GetField("CurrentScene", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				return (field != null) ? (field.GetValue(null) as string) : null;
			}
			catch
			{
				return null;
			}
		}

		private static string FromStats(out int levelNumber)
		{
			levelNumber = -1;
			try
			{
				Type type = Game.GetType("StatsManager");
				if (type == null)
				{
					return null;
				}
				Object val = Object.FindObjectOfType(type);
				if (val == (Object)null)
				{
					return null;
				}
				FieldInfo field = type.GetField("levelNumber", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field == null)
				{
					return null;
				}
				levelNumber = (int)field.GetValue(val);
				Type type2 = Game.GetType("GetMissionName");
				MethodInfo methodInfo = ((type2 != null) ? type2.GetMethod("GetMission", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(int) }, null) : null);
				return (methodInfo != null) ? (methodInfo.Invoke(null, new object[1] { levelNumber }) as string) : null;
			}
			catch
			{
				return null;
			}
		}
	}
	internal static class LevelProgression
	{
		private static readonly HashSet<int> _fired = new HashSet<int>();

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

		private static int _sceneHandle = -1;

		private static bool _sceneMatches;

		public static void OnSceneLoaded()
		{
			_fired.Clear();
			_firstSeen.Clear();
			_sceneHandle = -1;
		}

		public static void Tick(EnemyIdentifier eid)
		{
			if (!Cfg.OpenExitWithoutKill || !SceneMatches())
			{
				return;
			}
			int instanceID = ((Object)eid).GetInstanceID();
			if (!_fired.Contains(instanceID))
			{
				if (!_firstSeen.TryGetValue(instanceID, out var value))
				{
					_firstSeen[instanceID] = Time.time;
				}
				else if (!(Time.time - value < Mathf.Max(0f, Cfg.OpenExitDelay)))
				{
					_fired.Add(instanceID);
					FireDeathEvents(eid);
				}
			}
		}

		private static bool SceneMatches()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			if (((Scene)(ref activeScene)).handle != _sceneHandle)
			{
				_sceneHandle = ((Scene)(ref activeScene)).handle;
				_sceneMatches = false;
				string text = LevelId.Best() + " " + (((Scene)(ref activeScene)).name ?? string.Empty);
				string[] array = (Cfg.ExitLevels ?? string.Empty).Split(',');
				foreach (string text2 in array)
				{
					string text3 = text2.Trim();
					if (text3.Length > 0 && text.IndexOf(text3, StringComparison.OrdinalIgnoreCase) >= 0)
					{
						_sceneMatches = true;
						break;
					}
				}
				if (Cfg.DebugLogging)
				{
					FriendlyGabrielPlugin.Log.LogInfo((object)("Scene '" + text + "' exit-unlock active: " + _sceneMatches));
				}
			}
			return _sceneMatches;
		}

		private static void FireDeathEvents(EnemyIdentifier eid)
		{
			int num = 0;
			num += FireOn(((Component)eid).GetComponent<Gabriel>());
			num += FireOn(((Component)eid).GetComponent<GabrielSecond>());
			num += FireOn(eid);
			if (num == 0)
			{
				FriendlyGabrielPlugin.Log.LogWarning((object)("No death event found on " + ((object)Unsafe.As<EnemyType, EnemyType>(ref eid.enemyType)/*cast due to .constrained prefix*/).ToString() + "; the exit may not open. Enable Debug logging to list the event fields."));
			}
			else
			{
				FriendlyGabrielPlugin.Log.LogInfo((object)("Fired " + num + " death event(s) for " + ((object)Unsafe.As<EnemyType, EnemyType>(ref eid.enemyType)/*cast due to .constrained prefix*/).ToString()));
			}
		}

		private static int FireOn(object component)
		{
			if (component == null)
			{
				return 0;
			}
			int num = 0;
			Type type = component.GetType();
			while (type != null && type != typeof(MonoBehaviour) && type != typeof(object))
			{
				FieldInfo[] fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				foreach (FieldInfo fieldInfo in fields)
				{
					bool flag = fieldInfo.Name.IndexOf("death", StringComparison.OrdinalIgnoreCase) >= 0;
					string name = fieldInfo.FieldType.Name;
					if (Cfg.DebugLogging && (name == "UltrakillEvent" || name == "UnityEvent" || fieldInfo.FieldType == typeof(GameObject[])))
					{
						FriendlyGabrielPlugin.Log.LogInfo((object)("Found " + name + " " + type.Name + "." + fieldInfo.Name));
					}
					if (!flag)
					{
						continue;
					}
					try
					{
						if (name == "UltrakillEvent" || name == "UnityEvent")
						{
							object value = fieldInfo.GetValue(component);
							MethodInfo method = fieldInfo.FieldType.GetMethod("Invoke", Type.EmptyTypes);
							if (value != null && method != null)
							{
								method.Invoke(value, null);
								num++;
								FriendlyGabrielPlugin.Log.LogInfo((object)("Fired " + type.Name + "." + fieldInfo.Name));
							}
						}
						else
						{
							if (!(fieldInfo.FieldType == typeof(GameObject[])) || !(fieldInfo.GetValue(component) is GameObject[] array))
							{
								continue;
							}
							GameObject[] array2 = array;
							foreach (GameObject val in array2)
							{
								if ((Object)(object)val != (Object)null)
								{
									val.SetActive(true);
								}
							}
							num++;
							FriendlyGabrielPlugin.Log.LogInfo((object)("Activated " + array.Length + " object(s) from " + type.Name + "." + fieldInfo.Name));
							continue;
						}
					}
					catch (Exception ex)
					{
						FriendlyGabrielPlugin.Log.LogError((object)("Failed on " + fieldInfo.Name + ": " + ex.Message));
					}
				}
				type = type.BaseType;
			}
			return num;
		}
	}
	internal static class Patches
	{
		public static void EnemyIdentifier_LateUpdate_Postfix(EnemyIdentifier __instance)
		{
			CompanionManager.Tick(__instance);
		}

		public static void GabrielBase_FixedUpdate_Prefix(Component __instance)
		{
			CompanionManager.TickFromHook(__instance);
		}

		public static bool Gabriel_TakeDamage_Prefix()
		{
			return !CompanionManager.FirstIsImmortal;
		}

		public static bool GabrielSecond_TakeDamage_Prefix()
		{
			return !CompanionManager.SecondIsImmortal;
		}

		public static bool NewMovement_GetHurt_Prefix()
		{
			return !CompanionManager.PlayerShouldBeProtected();
		}

		public static bool EnemyIdentifier_DeliverDamage_Prefix(EnemyIdentifier __instance, GameObject target)
		{
			if (!CompanionManager.IsCompanion(__instance))
			{
				return true;
			}
			if (Cfg.Immortal)
			{
				return false;
			}
			return !((Object)(object)target != (Object)null) || !target.CompareTag("Player");
		}
	}
	[BepInPlugin("com.friendlygabriel.ultrakill", "Friendly Gabriel", "2.0.12")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInProcess("ULTRAKILL.exe")]
	public class FriendlyGabrielPlugin : BaseUnityPlugin
	{
		public const string Guid = "com.friendlygabriel.ultrakill";

		public const string PluginName = "Friendly Gabriel";

		public const string PluginVersion = "2.0.12";

		internal static ManualLogSource Log;

		private Harmony _harmony;

		private bool _errorLogged;

		private void Awake()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_005c: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			try
			{
				new ConfigBuilder("com.friendlygabriel.ultrakill", "Friendly Gabriel").BuildAll();
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Configgy registration failed: " + ex));
			}
			_harmony = new Harmony("com.friendlygabriel.ultrakill");
			int num = 0;
			int num2 = 6;
			if (TryPatch("GabrielBase", "FixedUpdate", "GabrielBase_FixedUpdate_Prefix", null))
			{
				num++;
				CompanionManager.LoopDrivesTick = false;
			}
			if (TryPatch("EnemyIdentifier", "LateUpdate", null, "EnemyIdentifier_LateUpdate_Postfix"))
			{
				num++;
			}
			if (TryPatch("Gabriel", "TakeDamage", "Gabriel_TakeDamage_Prefix", null))
			{
				num++;
			}
			if (TryPatch("GabrielSecond", "TakeDamage", "GabrielSecond_TakeDamage_Prefix", null))
			{
				num++;
			}
			if (TryPatch("EnemyIdentifier", "DeliverDamage", "EnemyIdentifier_DeliverDamage_Prefix", null))
			{
				num++;
			}
			if (TryPatch("NewMovement", "GetHurt", "NewMovement_GetHurt_Prefix", null))
			{
				num++;
			}
			SceneManager.sceneLoaded += delegate
			{
				CompanionManager.OnSceneLoaded();
			};
			Log.LogInfo((object)("=== Friendly Gabriel 2.0.12 loaded (" + num + "/" + num2 + " optional patches applied) ==="));
		}

		private void LateUpdate()
		{
			try
			{
				CompanionManager.TickAll();
				CompanionManager.MoveAll();
				SpawnManager.Tick();
			}
			catch (Exception ex)
			{
				if (!_errorLogged)
				{
					_errorLogged = true;
					Log.LogError((object)("Companion loop error (logged once): " + ex));
				}
			}
		}

		private bool TryPatch(string typeName, string methodName, string prefix, string postfix)
		{
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Type type = typeof(EnemyIdentifier).Assembly.GetType(typeName);
				if (type == null)
				{
					Log.LogWarning((object)("Type not found, skipped: " + typeName));
					return false;
				}
				Type type2 = type;
				while (type2 != null && type2 != typeof(object))
				{
					bool flag = false;
					MethodInfo[] methods = type2.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					foreach (MethodInfo methodInfo in methods)
					{
						if (!(methodInfo.Name != methodName))
						{
							_harmony.Patch((MethodBase)methodInfo, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Patches).GetMethod(prefix)), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Patches).GetMethod(postfix)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
							flag = true;
						}
					}
					if (flag)
					{
						if (type2 != type)
						{
							Log.LogInfo((object)(typeName + "." + methodName + " is declared on parent class " + type2.Name + ", patched there"));
						}
						return true;
					}
					type2 = type2.BaseType;
				}
				Log.LogWarning((object)("Method not found (also checked parent classes), skipped: " + typeName + "." + methodName));
				return false;
			}
			catch (Exception ex)
			{
				Log.LogError((object)("Patch failed for " + typeName + "." + methodName + ": " + ex.Message));
				return false;
			}
		}

		private void OnDestroy()
		{
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
		}
	}
	internal static class SpawnManager
	{
		private static float _nextAttempt;

		private static int _attempts;

		private static GameObject _spawned;

		private static bool _checkLogged;

		private static string _lastCandidates;

		public static void OnSceneLoaded()
		{
			_nextAttempt = Time.time + 4f;
			_attempts = 0;
			_spawned = null;
			_checkLogged = false;
		}

		public static void Tick()
		{
			//IL_0194: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: 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)
			if (!Cfg.Enabled || !Cfg.SpawnCompanion || Time.time < _nextAttempt)
			{
				return;
			}
			_nextAttempt = Time.time + 3f;
			string candidates;
			bool flag = LevelQualifies(out candidates);
			bool flag2 = CompanionManager.HasAnyGabriel();
			if (!_checkLogged || candidates != _lastCandidates)
			{
				_checkLogged = true;
				_lastCandidates = candidates;
				FriendlyGabrielPlugin.Log.LogInfo((object)("Spawn check: " + candidates + " -> after '" + Cfg.SpawnAfterLevel + "' = " + flag + ", Gabriel already in level = " + flag2));
			}
			if (!flag || flag2 || (Object)(object)_spawned != (Object)null || _attempts >= 3)
			{
				return;
			}
			Transform val = CompanionManager.PlayerTransform();
			if (!((Object)(object)val == (Object)null))
			{
				_attempts++;
				int wantId = (Cfg.SpawnGabrielSecond ? 28 : 16);
				GameObject val2 = FindPrefab(wantId);
				if ((Object)(object)val2 == (Object)null)
				{
					FriendlyGabrielPlugin.Log.LogWarning((object)("Spawn attempt " + _attempts + "/3: could not find a Gabriel prefab (see 'Prefab keys' in the Spawning section)."));
					return;
				}
				GameObject val3 = Object.Instantiate<GameObject>(val2);
				val3.transform.position = val.position + val.right * Mathf.Max(1f, Cfg.SpawnDistance) + new Vector3(0f, 1f, 0f);
				val3.SetActive(true);
				_spawned = val3;
				FriendlyGabrielPlugin.Log.LogInfo((object)("Spawned companion Gabriel from prefab '" + ((Object)val2).name + "'"));
			}
		}

		private static bool LevelQualifies(out string candidates)
		{
			string id = LevelId.Best(out candidates);
			if (!LevelId.TryParse(id, out var layer, out var number))
			{
				return false;
			}
			if (!LevelId.TryParse(Cfg.SpawnAfterLevel, out var layer2, out var number2))
			{
				return false;
			}
			return layer > layer2 || (layer == layer2 && number > number2);
		}

		private static GameObject FindPrefab(int wantId)
		{
			//IL_001b: 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_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004b: Invalid comparison between Unknown and I4
			//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Invalid comparison between Unknown and I4
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Invalid comparison between Unknown and I4
			GameObject[] array = Resources.FindObjectsOfTypeAll<GameObject>();
			foreach (GameObject val in array)
			{
				if ((Object)(object)val == (Object)null)
				{
					continue;
				}
				Scene scene = val.scene;
				if (!((Scene)(ref scene)).IsValid())
				{
					EnemyIdentifier component = val.GetComponent<EnemyIdentifier>();
					if ((Object)(object)component != (Object)null && (int)component.enemyType == wantId)
					{
						FriendlyGabrielPlugin.Log.LogInfo((object)("Found loaded Gabriel prefab: " + ((Object)val).name));
						return val;
					}
				}
			}
			string[] array2 = (Cfg.PrefabKeys ?? "").Split(',');
			foreach (string text in array2)
			{
				string text2 = text.Trim();
				if (text2.Length == 0)
				{
					continue;
				}
				GameObject val2 = LoadAddressable(text2);
				if (!((Object)(object)val2 == (Object)null))
				{
					EnemyIdentifier component2 = val2.GetComponent<EnemyIdentifier>();
					if ((Object)(object)component2 != (Object)null && (int)component2.enemyType == wantId)
					{
						FriendlyGabrielPlugin.Log.LogInfo((object)("Loaded Gabriel prefab via Addressables key: " + text2));
						return val2;
					}
				}
			}
			foreach (string item in DiscoverGabrielKeys())
			{
				GameObject val3 = LoadAddressable(item);
				if (!((Object)(object)val3 == (Object)null))
				{
					EnemyIdentifier component3 = val3.GetComponent<EnemyIdentifier>();
					if ((Object)(object)component3 != (Object)null && (int)component3.enemyType == wantId)
					{
						FriendlyGabrielPlugin.Log.LogInfo((object)("Loaded Gabriel prefab via discovered key: " + item));
						return val3;
					}
				}
			}
			return null;
		}

		private static List<string> DiscoverGabrielKeys()
		{
			List<string> list = new List<string>();
			try
			{
				Type type = Type.GetType("UnityEngine.AddressableAssets.Addressables, Unity.Addressables");
				Type type2 = Type.GetType("UnityEngine.AddressableAssets.ResourceLocators.IResourceLocator, Unity.Addressables");
				if (type == null || type2 == null)
				{
					FriendlyGabrielPlugin.Log.LogInfo((object)"Addressables types not found, cannot discover keys");
					return list;
				}
				PropertyInfo property = type.GetProperty("ResourceLocators", BindingFlags.Static | BindingFlags.Public);
				PropertyInfo property2 = type2.GetProperty("Keys");
				IEnumerable enumerable = ((property != null) ? (property.GetValue(null, null) as IEnumerable) : null);
				if (enumerable == null || property2 == null)
				{
					return list;
				}
				foreach (object item in enumerable)
				{
					if (!(property2.GetValue(item, null) is IEnumerable enumerable2))
					{
						continue;
					}
					foreach (object item2 in enumerable2)
					{
						if (item2 is string text && text.IndexOf("gabriel", StringComparison.OrdinalIgnoreCase) >= 0 && text.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase) && !list.Contains(text))
						{
							list.Add(text);
						}
					}
				}
				FriendlyGabrielPlugin.Log.LogInfo((object)("Addressables keys that look like Gabriel prefabs (" + list.Count + "):"));
				for (int i = 0; i < list.Count && i < 25; i++)
				{
					FriendlyGabrielPlugin.Log.LogInfo((object)("  " + list[i]));
				}
			}
			catch (Exception ex)
			{
				FriendlyGabrielPlugin.Log.LogInfo((object)("Key discovery failed: " + (ex.InnerException ?? ex).Message));
			}
			return list;
		}

		private static GameObject LoadAddressable(string key)
		{
			try
			{
				Type type = Type.GetType("UnityEngine.AddressableAssets.Addressables, Unity.Addressables");
				if (type == null)
				{
					return null;
				}
				MethodInfo methodInfo = null;
				MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public);
				foreach (MethodInfo methodInfo2 in methods)
				{
					ParameterInfo[] parameters = methodInfo2.GetParameters();
					if (methodInfo2.Name == "LoadAssetAsync" && methodInfo2.IsGenericMethodDefinition && parameters.Length == 1 && parameters[0].ParameterType == typeof(object))
					{
						methodInfo = methodInfo2;
						break;
					}
				}
				if (methodInfo == null)
				{
					return null;
				}
				object obj = methodInfo.MakeGenericMethod(typeof(GameObject)).Invoke(null, new object[1] { key });
				MethodInfo method = obj.GetType().GetMethod("WaitForCompletion");
				return (GameObject)((method != null) ? /*isinst with value type is only supported in some contexts*/: null);
			}
			catch (Exception ex)
			{
				Exception ex2 = ex.InnerException ?? ex;
				FriendlyGabrielPlugin.Log.LogInfo((object)("Addressables key '" + key + "' did not load: " + ex2.Message));
				return null;
			}
		}
	}
}