Decompiled source of AlarmSpawnTeller v0.5.0

plugins/AlarmSpawnTeller/AlarmSpawnTeller.dll

Decompiled a day ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text.Json;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using Enemies;
using HarmonyLib;
using Il2CppInterop.Runtime.Injection;
using Localization;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("AlarmSpawnTeller")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.5.0")]
[assembly: AssemblyInformationalVersion("0.5.0")]
[assembly: AssemblyProduct("AlarmSpawnTeller")]
[assembly: AssemblyTitle("AlarmSpawnTeller")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.5.0.0")]
[module: UnverifiableCode]
[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 AlarmSpawnTeller
{
	internal enum SpawnSource
	{
		UnknownDynamic,
		DoorAlarm,
		ErrorAlarm,
		ScoutScream,
		SurvivalWave,
		BloodDoor
	}
	internal enum SpawnSourceKind
	{
		None,
		Dynamic,
		BloodDoor
	}
	[BepInPlugin("logic.gtfo.alarmspawnteller", "Alarm Spawn Teller", "0.5.0")]
	public class Plugin : BasePlugin
	{
		private const string PluginGuid = "logic.gtfo.alarmspawnteller";

		private const string PluginName = "Alarm Spawn Teller";

		private const string PluginVersion = "0.5.0";

		internal static ManualLogSource LogSource;

		public override void Load()
		{
			//IL_021a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0224: Expected O, but got Unknown
			LogSource = ((BasePlugin)this).Log;
			((BasePlugin)this).Log.LogInfo((object)"[Alarm Spawn Teller] Installing patches...");
			ConfigEntry<bool> debugSourceMode = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "DebugSourceMode", false, "Enables Blood Door spawn source investigation logs. This does not enable Blood Door classification.");
			ConfigEntry<bool> enableBloodDoorCandidateClassification = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "EnableBloodDoorCandidateClassification", false, "Enables the experimental Blood Door candidate rule for tracker routing.");
			ConfigEntry<string> bloodDoorSettingsNameKeywords = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "BloodDoorSettingsNameKeywords", "MainframeRush_ModificationWave,Blood,Door", "Comma-separated settings.name keywords used by the experimental Blood Door candidate rule.");
			ConfigEntry<bool> enableSpawnDataBloodDoorCandidateClassification = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "EnableSpawnDataBloodDoorCandidateClassification", true, "Enables experimental pEnemyGroupSpawnData groupType routing to the Blood Door tracker.");
			ConfigEntry<string> bloodDoorSpawnDataGroupTypes = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "BloodDoorSpawnDataGroupTypes", "Hunters", "Comma-separated pEnemyGroupSpawnData groupType values used by the experimental Blood Door candidate rule.");
			ConfigEntry<string> bloodDoorSpawnDataSpawnTypes = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "BloodDoorSpawnDataSpawnTypes", "OnLine,RandomInArea", "Comma-separated pEnemyGroupSpawnData spawnType values allowed by the experimental Blood Door candidate rule.");
			ConfigEntry<string> excludedBloodDoorSpawnDataSpawnTypes = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "ExcludedBloodDoorSpawnDataSpawnTypes", "Birther", "Comma-separated pEnemyGroupSpawnData spawnType values excluded from the experimental Blood Door candidate rule.");
			ConfigEntry<bool> enableEnemySetupProbe = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "EnableEnemySetupProbe", false, "Logs matching EnemyAgent.Setup decisions without changing spawn tracking.");
			ConfigEntry<string> enemySetupProbeNameKeywords = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "EnemySetupProbeNameKeywords", "Tank,BossTank,Tank_Boss,Pouncer", "Comma-separated enemy name keywords used by the EnemyAgent.Setup diagnostic probe.");
			ConfigEntry<bool> enableSpecialEnemyFallbackTracking = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "EnableSpecialEnemyFallbackTracking", true, "Enables experimental Dynamic tracker fallback for configured special enemies.");
			ConfigEntry<string> specialEnemyFallbackNameKeywords = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "SpecialEnemyFallbackNameKeywords", "Tank,BossTank,Tank_Boss,Pouncer", "Comma-separated enemy name keywords eligible for special enemy fallback tracking.");
			ConfigEntry<string> specialEnemyFallbackTarget = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "SpecialEnemyFallbackTarget", "Dynamic", "Target tracker for special enemy fallback. Only Dynamic is currently supported.");
			ConfigEntry<bool> specialEnemyFallbackRequireAgressiveMode = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "SpecialEnemyFallbackRequireAgressiveMode", true, "Requires spawnData mode to be Agressive or Aggressive for special enemy fallback tracking.");
			ConfigEntry<bool> dynamicAutoHideEnabled = ((BasePlugin)this).Config.Bind<bool>("HUD", "DynamicAutoHideEnabled", true, "Removes Dynamic Spawns enemy-type rows after that type remains at zero alive.");
			ConfigEntry<float> dynamicAutoHideAfterSeconds = ((BasePlugin)this).Config.Bind<float>("HUD", "DynamicAutoHideAfterSeconds", 5f, "Seconds a Dynamic enemy type must remain at zero alive before that type row is removed. Minimum 1 second.");
			EnemyGroupSpawnPatch.ConfigureSourceInvestigation(debugSourceMode, enableBloodDoorCandidateClassification, bloodDoorSettingsNameKeywords, enableSpawnDataBloodDoorCandidateClassification, bloodDoorSpawnDataGroupTypes, bloodDoorSpawnDataSpawnTypes, excludedBloodDoorSpawnDataSpawnTypes, enableEnemySetupProbe, enemySetupProbeNameKeywords, enableSpecialEnemyFallbackTracking, specialEnemyFallbackNameKeywords, specialEnemyFallbackTarget, specialEnemyFallbackRequireAgressiveMode);
			EnemyGroupSpawnPatch.ConfigureDynamicAutoHide(dynamicAutoHideEnabled, dynamicAutoHideAfterSeconds);
			Localizer.LoadLocalization();
			InstallTicker();
			EnemyGroupSpawnPatch.Apply(new Harmony("logic.gtfo.alarmspawnteller"));
			((BasePlugin)this).Log.LogInfo((object)"Alarm Spawn Teller loaded successfully.");
		}

		private void InstallTicker()
		{
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Expected O, but got Unknown
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Expected O, but got Unknown
			bool flag = default(bool);
			try
			{
				ClassInjector.RegisterTypeInIl2Cpp<AlarmSpawnTicker>();
			}
			catch (Exception ex)
			{
				ManualLogSource log = ((BasePlugin)this).Log;
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(70, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] AlarmSpawnTicker registration skipped or failed: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				log.LogWarning(val);
			}
			try
			{
				((BasePlugin)this).AddComponent<AlarmSpawnTicker>();
				((BasePlugin)this).Log.LogInfo((object)"[Alarm Spawn Teller] AlarmSpawnTicker added.");
			}
			catch (Exception ex2)
			{
				ManualLogSource log2 = ((BasePlugin)this).Log;
				BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(53, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[Alarm Spawn Teller] Failed to add AlarmSpawnTicker: ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex2);
				}
				log2.LogError(val2);
			}
		}
	}
	public sealed class AlarmSpawnTicker : MonoBehaviour
	{
		private enum HudDisplayState
		{
			Idle,
			ActiveSession,
			FinalSummary
		}

		private const float LanguageRefreshIntervalSeconds = 3f;

		private const float HudBoxWidth = 320f;

		private const float HudRightMargin = 30f;

		private const float HudBoxGap = 8f;

		private static readonly List<float> PendingScanTimes = new List<float>();

		private static bool HasLoggedUpdate;

		private static bool IsHudDisabled;

		private static string DynamicHudMessage;

		private static string BloodDoorHudMessage;

		private static float DynamicHudMessageEndTime;

		private static GUIStyle HudStyle;

		private static HudDisplayState HudState;

		private static float NextLanguageRefreshTime;

		public AlarmSpawnTicker(IntPtr pointer)
			: base(pointer)
		{
		}

		internal static void ScheduleScan(float delaySeconds)
		{
			PendingScanTimes.Add(Time.time + delaySeconds);
			Plugin.LogSource.LogInfo((object)"[Alarm Spawn Teller] Scheduled delayed scan.");
		}

		internal static bool HasPendingScans()
		{
			return PendingScanTimes.Count > 0;
		}

		internal static void ShowAlarmSpawnMessage(string message, float durationSeconds = 8f)
		{
			if (!IsHudDisabled)
			{
				DynamicHudMessage = message;
				DynamicHudMessageEndTime = Time.time + durationSeconds;
				HudState = HudDisplayState.FinalSummary;
			}
		}

		internal static void ShowActiveAlarmSpawnMessage(string message)
		{
			if (!IsHudDisabled)
			{
				DynamicHudMessage = message;
				HudState = HudDisplayState.ActiveSession;
			}
		}

		internal static void HideAlarmSpawnMessage()
		{
			DynamicHudMessage = null;
			DynamicHudMessageEndTime = 0f;
			HudState = HudDisplayState.Idle;
		}

		internal static void ShowBloodDoorSpawnMessage(string message)
		{
			if (!IsHudDisabled)
			{
				BloodDoorHudMessage = message;
			}
		}

		internal static void HideBloodDoorSpawnMessage()
		{
			BloodDoorHudMessage = null;
		}

		private void Update()
		{
			float time = Time.time;
			if (!HasLoggedUpdate)
			{
				HasLoggedUpdate = true;
				EnemyGroupSpawnPatch.MarkStartupTime();
				if (EnemyGroupSpawnPatch.DebugModeEnabled)
				{
					Plugin.LogSource.LogInfo((object)"[Alarm Spawn Teller] AlarmSpawnTicker.Update is running.");
				}
			}
			if (Localizer.IsAutoLanguage && time >= NextLanguageRefreshTime)
			{
				NextLanguageRefreshTime = time + 3f;
				Localizer.RefreshAutoLanguage();
			}
			EnemyGroupSpawnPatch.ProcessCaptureWindowTimeout();
			EnemyGroupSpawnPatch.ProcessPendingAgentRecordResolves();
			EnemyGroupSpawnPatch.ProcessTrackedAlarmEnemyAliveStates();
			EnemyGroupSpawnPatch.ProcessDynamicAutoHide();
			EnemyGroupSpawnPatch.ProcessAlarmSessionTimeout();
			if (PendingScanTimes.Count == 0)
			{
				return;
			}
			for (int num = PendingScanTimes.Count - 1; num >= 0; num--)
			{
				if (!(time < PendingScanTimes[num]))
				{
					PendingScanTimes.RemoveAt(num);
					if (EnemyGroupSpawnPatch.DebugModeEnabled)
					{
						Plugin.LogSource.LogInfo((object)"[Alarm Spawn Teller] Running delayed scan.");
					}
					EnemyGroupSpawnPatch.ScanForNewEnemies();
				}
			}
		}

		private void OnGUI()
		{
			//IL_0144: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Expected O, but got Unknown
			if (IsHudDisabled)
			{
				return;
			}
			if (HudState == HudDisplayState.FinalSummary && Time.time >= DynamicHudMessageEndTime)
			{
				HudState = HudDisplayState.Idle;
				DynamicHudMessage = null;
			}
			try
			{
				if (HudStyle == null)
				{
					HudStyle = CreateHudStyle();
				}
				List<string> list = new List<string>(2);
				if (HudState != HudDisplayState.Idle && HasHudStatisticsContent(DynamicHudMessage))
				{
					list.Add(DynamicHudMessage);
				}
				if (HasHudStatisticsContent(BloodDoorHudMessage))
				{
					list.Add(BloodDoorHudMessage);
				}
				if (list.Count != 0)
				{
					float[] array = list.Select(GetHudBoxHeight).ToArray();
					float num = array.Sum() + 8f * (float)(list.Count - 1);
					float x = Mathf.Max(0f, (float)Screen.width - 320f - 30f);
					float num2 = Mathf.Max(0f, ((float)Screen.height - num) * 0.5f);
					for (int i = 0; i < list.Count; i++)
					{
						DrawHudBox(list[i], x, num2, array[i]);
						num2 += array[i] + 8f;
					}
				}
			}
			catch (Exception ex)
			{
				IsHudDisabled = true;
				ManualLogSource logSource = Plugin.LogSource;
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(61, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] HUD overlay disabled after OnGUI error: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
				}
				logSource.LogWarning(val);
			}
		}

		private static float GetHudBoxHeight(string message)
		{
			int num = message.Count((char character) => character == '\n') + 1;
			return Math.Max(70f, 32f + (float)num * 24f);
		}

		private static bool HasHudStatisticsContent(string message)
		{
			if (string.IsNullOrWhiteSpace(message))
			{
				return false;
			}
			return message.Split('\n', StringSplitOptions.RemoveEmptyEntries).Count((string line) => !string.IsNullOrWhiteSpace(line)) >= 2;
		}

		private static void DrawHudBox(string message, float x, float y, float height)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			GUI.Box(new Rect(x, y, 320f, height), message, HudStyle);
		}

		private static GUIStyle CreateHudStyle()
		{
			//IL_000a: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: 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_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Expected O, but got Unknown
			GUIStyle val = new GUIStyle(GUI.skin.box)
			{
				alignment = (TextAnchor)0,
				fontSize = 20,
				wordWrap = true,
				padding = new RectOffset(14, 14, 10, 10)
			};
			val.normal.textColor = Color.white;
			return val;
		}
	}
	internal static class Localizer
	{
		private sealed class LocalizationConfig
		{
			public string Language { get; set; }

			public Dictionary<string, Dictionary<string, string>> HudTexts { get; set; }

			public Dictionary<string, EnemyNameEntry> EnemyNames { get; set; }
		}

		private sealed class EnemyNameEntry
		{
			public string EnemyDataBlockID { get; set; }

			public string EnemyDataName { get; set; }

			public string[] Aliases { get; set; }

			public string Chinese { get; set; }

			public string English { get; set; }

			public string Japanese { get; set; }
		}

		private const string AutoLanguage = "Auto";

		private const string EnglishFallbackLanguage = "English";

		private const string LocalizationFileName = "AlarmSpawnTeller.localization.json";

		private static LocalizationConfig Config;

		private static bool HasLoggedAutoLanguageWarning;

		internal static string ConfiguredLanguage { get; private set; } = "Auto";

		internal static string ResolvedLanguage { get; private set; } = "English";

		internal static bool IsAutoLanguage { get; private set; } = true;

		internal static void LoadLocalization()
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Expected O, but got Unknown
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			bool flag = default(bool);
			try
			{
				string text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? AppContext.BaseDirectory, "AlarmSpawnTeller.localization.json");
				if (!File.Exists(text))
				{
					ManualLogSource logSource = Plugin.LogSource;
					if (logSource != null)
					{
						BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(94, 1, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Localization JSON not found beside plugin DLL: ");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; using built-in fallback.");
						}
						logSource.LogWarning(val);
					}
					Config = CreateMinimalFallbackConfig();
					ResolveConfiguredLanguage();
				}
				else
				{
					Config = JsonSerializer.Deserialize<LocalizationConfig>(File.ReadAllText(text)) ?? CreateMinimalFallbackConfig();
					EnsureDefaults(Config);
					ResolveConfiguredLanguage();
				}
			}
			catch (Exception ex)
			{
				ManualLogSource logSource = Plugin.LogSource;
				if (logSource != null)
				{
					BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(80, 1, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Failed to load localization JSON; using built-in fallback. ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
					}
					logSource.LogWarning(val);
				}
				Config = CreateMinimalFallbackConfig();
				ResolveConfiguredLanguage();
			}
		}

		internal unsafe static void RefreshAutoLanguage()
		{
			//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_0047: Unknown result type (might be due to invalid IL or missing references)
			if (!IsAutoLanguage)
			{
				return;
			}
			try
			{
				ITextLocalizationService textLocalizationService = Text.TextLocalizationService;
				if (textLocalizationService == null)
				{
					SetAutoLanguageFallback("GTFO text localization service is not initialized.");
					return;
				}
				Language currentLanguage = textLocalizationService.CurrentLanguage;
				string text = ((object)(*(Language*)(&currentLanguage))/*cast due to .constrained prefix*/).ToString();
				if (string.IsNullOrWhiteSpace(text))
				{
					SetAutoLanguageFallback("GTFO current language was empty.");
					return;
				}
				string text2 = MapGameLanguage(currentLanguage) ?? NormalizeLanguage(text);
				if (text2 == null)
				{
					SetAutoLanguageFallback("GTFO language '" + text + "' is not supported.");
					return;
				}
				UpdateResolvedLanguage(text2);
				HasLoggedAutoLanguageWarning = false;
			}
			catch (Exception ex)
			{
				SetAutoLanguageFallback("Failed to read GTFO current language: " + ex.Message);
			}
		}

		internal static string GetText(string key, params object[] args)
		{
			string text = ResolveLocalizedValue(Config?.HudTexts, key, key);
			if (args == null || args.Length == 0)
			{
				return text;
			}
			try
			{
				return string.Format(text, args);
			}
			catch
			{
				return text;
			}
		}

		internal static string GetEnemyDisplayName(string rawEnemyDataName)
		{
			return GetEnemyDisplayName(null, rawEnemyDataName);
		}

		internal static string GetEnemyDisplayName(string enemyDataID, string rawEnemyDataName)
		{
			string text = StripEnemyIdPrefix(rawEnemyDataName);
			EnemyNameEntry enemyNameEntry = FindEnemyNameByRawName(text);
			if (enemyNameEntry != null)
			{
				return ResolveEnemyName(enemyNameEntry, text);
			}
			string text2 = NormalizeEnemyDataID(enemyDataID);
			if (!string.IsNullOrWhiteSpace(text2) && Config?.EnemyNames != null)
			{
				foreach (KeyValuePair<string, EnemyNameEntry> enemyName in Config.EnemyNames)
				{
					if (string.Equals(enemyName.Value?.EnemyDataBlockID, text2, StringComparison.OrdinalIgnoreCase))
					{
						return ResolveEnemyName(enemyName.Value, text);
					}
				}
			}
			return CleanEnemyName(text);
		}

		private static string NormalizeEnemyDataID(string enemyDataID)
		{
			if (string.IsNullOrWhiteSpace(enemyDataID))
			{
				return null;
			}
			string text = enemyDataID.Trim();
			if (!uint.TryParse(text, out var result))
			{
				return text;
			}
			return result.ToString();
		}

		private static EnemyNameEntry FindEnemyNameByRawName(string rawName)
		{
			if (Config?.EnemyNames == null || string.IsNullOrWhiteSpace(rawName))
			{
				return null;
			}
			if (Config.EnemyNames.TryGetValue(rawName, out var value))
			{
				return value;
			}
			foreach (KeyValuePair<string, EnemyNameEntry> enemyName in Config.EnemyNames)
			{
				EnemyNameEntry value2 = enemyName.Value;
				if (value2 != null)
				{
					if (string.Equals(value2.EnemyDataName, rawName, StringComparison.OrdinalIgnoreCase))
					{
						return value2;
					}
					if (value2.Aliases != null && value2.Aliases.Any((string alias) => !string.IsNullOrWhiteSpace(alias) && string.Equals(alias, rawName, StringComparison.OrdinalIgnoreCase)))
					{
						return value2;
					}
				}
			}
			return null;
		}

		private static string ResolveEnemyName(EnemyNameEntry entry, string fallbackRawName)
		{
			if (entry == null)
			{
				return CleanEnemyName(fallbackRawName);
			}
			string resolvedLanguage = ResolvedLanguage;
			string text = ((resolvedLanguage == "Chinese") ? entry.Chinese : ((!(resolvedLanguage == "Japanese")) ? entry.English : entry.Japanese));
			string text2 = text;
			if (!string.IsNullOrWhiteSpace(text2))
			{
				return text2;
			}
			if (!string.IsNullOrWhiteSpace(entry.English))
			{
				return entry.English;
			}
			return CleanEnemyName(entry.EnemyDataName ?? fallbackRawName);
		}

		internal static string GetSourceDisplayName(SpawnSource source)
		{
			return GetText($"Source_{source}");
		}

		private static string ResolveLocalizedValue(Dictionary<string, Dictionary<string, string>> table, string key, string fallback)
		{
			if (table == null || string.IsNullOrWhiteSpace(key) || !table.TryGetValue(key, out var value) || value == null)
			{
				return fallback;
			}
			if (value.TryGetValue(ResolvedLanguage, out var value2) && !string.IsNullOrWhiteSpace(value2))
			{
				return value2;
			}
			if (value.TryGetValue("English", out value2) && !string.IsNullOrWhiteSpace(value2))
			{
				return value2;
			}
			return fallback;
		}

		private static string StripEnemyIdPrefix(string rawEnemyDataName)
		{
			string text = rawEnemyDataName ?? string.Empty;
			int num = text.IndexOf(" / ", StringComparison.Ordinal);
			if (num >= 0 && num + 3 < text.Length)
			{
				text = text.Substring(num + 3);
			}
			return text;
		}

		private static string CleanEnemyName(string rawEnemyDataName)
		{
			string text = StripEnemyIdPrefix(rawEnemyDataName);
			return text switch
			{
				"Striker_Big_Wave" => "Big Striker", 
				"Shooter_Wave" => "Shooter", 
				"Striker_Wave" => "Striker", 
				_ => text.Replace("_Wave", string.Empty), 
			};
		}

		private static void EnsureDefaults(LocalizationConfig config)
		{
			LocalizationConfig localizationConfig = CreateMinimalFallbackConfig();
			config.Language = (string.IsNullOrWhiteSpace(config.Language) ? localizationConfig.Language : config.Language);
			LocalizationConfig localizationConfig2 = config;
			if (localizationConfig2.HudTexts == null)
			{
				Dictionary<string, Dictionary<string, string>> dictionary = (localizationConfig2.HudTexts = new Dictionary<string, Dictionary<string, string>>());
			}
			localizationConfig2 = config;
			if (localizationConfig2.EnemyNames == null)
			{
				Dictionary<string, EnemyNameEntry> dictionary3 = (localizationConfig2.EnemyNames = new Dictionary<string, EnemyNameEntry>());
			}
			MergeMissing(config.HudTexts, localizationConfig.HudTexts);
			MergeMissingEnemyNames(config.EnemyNames, localizationConfig.EnemyNames);
		}

		private static void MergeMissing(Dictionary<string, Dictionary<string, string>> target, Dictionary<string, Dictionary<string, string>> defaults)
		{
			foreach (KeyValuePair<string, Dictionary<string, string>> @default in defaults)
			{
				if (!target.TryGetValue(@default.Key, out var value) || value == null)
				{
					target[@default.Key] = new Dictionary<string, string>(@default.Value);
					continue;
				}
				foreach (KeyValuePair<string, string> item in @default.Value)
				{
					value.TryAdd(item.Key, item.Value);
				}
			}
		}

		private static void MergeMissingEnemyNames(Dictionary<string, EnemyNameEntry> target, Dictionary<string, EnemyNameEntry> defaults)
		{
			foreach (KeyValuePair<string, EnemyNameEntry> @default in defaults)
			{
				target.TryAdd(@default.Key, @default.Value);
			}
		}

		private static string SerializeConfig(LocalizationConfig config)
		{
			return JsonSerializer.Serialize(config, new JsonSerializerOptions
			{
				WriteIndented = true
			});
		}

		private static LocalizationConfig CreateMinimalFallbackConfig()
		{
			return new LocalizationConfig
			{
				Language = "Auto",
				HudTexts = new Dictionary<string, Dictionary<string, string>>
				{
					["Source_SurvivalWave"] = Text("动态刷怪", "Dynamic spawns", "動的スポーン"),
					["Source_UnknownDynamic"] = Text("其他动态刷怪", "Other dynamic spawns", "その他の動的スポーン"),
					["Source_DoorAlarm"] = Text("开门警报", "Door alarm", "ドアアラーム"),
					["Source_ErrorAlarm"] = Text("无限警报", "Error alarm", "エラーアラーム"),
					["Source_ScoutScream"] = Text("哨兵叫怪", "Scout scream", "スカウト叫び"),
					["SourceSummaryLine"] = Text("{0}存活:{1} / 已刷新:{2}", "{0} alive: {1} / spawned: {2}", "{0}生存:{1} / 出現:{2}"),
					["EnemyCountLine"] = Text("{0}:{1} / {2}只", "{0}: {1} / {2}", "{0}:{1} / {2}体"),
					["BloodDoorSpawns"] = Text("血门刷怪", "Blood Door Spawns", "Blood Door Spawns"),
					["BloodDoorAliveLine"] = Text("存活:{0} / 已生成:{1}", "Alive: {0} / Spawned: {1}", "Alive: {0} / Spawned: {1}")
				},
				EnemyNames = new Dictionary<string, EnemyNameEntry>
				{
					["Shooter_Wave"] = EnemyName("11", "Shooter_Wave", Array.Empty<string>(), "射手", "Shooter", "Shooter"),
					["Striker_Wave"] = EnemyName("13", "Striker_Wave", Array.Empty<string>(), "近战怪", "Striker", "Striker"),
					["Striker_Big_Wave"] = EnemyName("16", "Striker_Big_Wave", Array.Empty<string>(), "大近战", "Big Striker", "Big Striker")
				}
			};
		}

		private static void ResolveConfiguredLanguage()
		{
			ConfiguredLanguage = (string.IsNullOrWhiteSpace(Config?.Language) ? "Auto" : Config.Language.Trim());
			IsAutoLanguage = string.Equals(ConfiguredLanguage, "Auto", StringComparison.OrdinalIgnoreCase);
			if (IsAutoLanguage)
			{
				ResolvedLanguage = "English";
				RefreshAutoLanguage();
			}
			else
			{
				ResolvedLanguage = NormalizeLanguage(ConfiguredLanguage) ?? "English";
			}
		}

		private static string NormalizeLanguage(string language)
		{
			if (string.IsNullOrWhiteSpace(language))
			{
				return null;
			}
			switch (language.Trim().Replace("_", string.Empty).Replace("-", string.Empty)
				.Replace(" ", string.Empty)
				.ToLowerInvariant())
			{
			case "english":
			case "en":
			case "enus":
				return "English";
			case "chinese":
			case "zh":
			case "zhtw":
			case "zhcn":
			case "chinesesimplified":
			case "simplifiedchinese":
			case "chinesetraditional":
			case "traditionalchinese":
				return "Chinese";
			case "ja":
			case "jajp":
			case "japanese":
				return "Japanese";
			default:
				return null;
			}
		}

		private static string MapGameLanguage(Language language)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0004: 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_001d: Expected I4, but got Unknown
			if ((int)language != 1)
			{
				return (language - 9) switch
				{
					3 => "Chinese", 
					2 => "Chinese", 
					0 => "Japanese", 
					_ => null, 
				};
			}
			return "English";
		}

		private static void SetAutoLanguageFallback(string reason)
		{
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Expected O, but got Unknown
			UpdateResolvedLanguage("English");
			if (HasLoggedAutoLanguageWarning)
			{
				return;
			}
			HasLoggedAutoLanguageWarning = true;
			ManualLogSource logSource = Plugin.LogSource;
			if (logSource != null)
			{
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(46, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(reason);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" Falling back to English.");
				}
				logSource.LogWarning(val);
			}
		}

		private static void UpdateResolvedLanguage(string language)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			if (string.Equals(ResolvedLanguage, language, StringComparison.Ordinal))
			{
				return;
			}
			ResolvedLanguage = language;
			ManualLogSource logSource = Plugin.LogSource;
			if (logSource != null)
			{
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(56, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Localization language resolved to ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ResolvedLanguage);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(".");
				}
				logSource.LogInfo(val);
			}
		}

		private static EnemyNameEntry EnemyName(string enemyDataBlockID, string enemyDataName, string[] aliases, string chinese, string english, string japanese)
		{
			return new EnemyNameEntry
			{
				EnemyDataBlockID = enemyDataBlockID,
				EnemyDataName = enemyDataName,
				Aliases = (aliases ?? Array.Empty<string>()),
				Chinese = chinese,
				English = english,
				Japanese = japanese
			};
		}

		private static Dictionary<string, string> Text(string chinese, string english, string japanese)
		{
			return new Dictionary<string, string>
			{
				["Chinese"] = chinese,
				["English"] = english,
				["Japanese"] = japanese
			};
		}
	}
	internal static class EnemyGroupSpawnPatch
	{
		private sealed class SpawnCaptureRecord
		{
			internal string GroupType { get; private init; }

			internal string SpawnType { get; private init; }

			internal string PersistentGameDataID { get; private init; }

			internal string PersistentWavePopDataID { get; private init; }

			internal string SurvivalWaveReplicatorKey { get; private init; }

			internal string PopulationScore { get; private init; }

			internal string DebugEnemyMode { get; private init; }

			internal bool IsLikelyHibernating
			{
				get
				{
					if (!string.Equals(GroupType, "Hibernating", StringComparison.OrdinalIgnoreCase))
					{
						return string.Equals(DebugEnemyMode, "Hibernate", StringComparison.OrdinalIgnoreCase);
					}
					return true;
				}
			}

			internal static SpawnCaptureRecord FromSpawnData(object spawnData)
			{
				return new SpawnCaptureRecord
				{
					GroupType = ReadSpawnDataValue(spawnData, "groupType"),
					SpawnType = ReadSpawnDataValue(spawnData, "spawnType"),
					PersistentGameDataID = ReadSpawnDataValue(spawnData, "persistentGameDataID"),
					PersistentWavePopDataID = ReadSpawnDataValue(spawnData, "persistentWavePopDataID"),
					SurvivalWaveReplicatorKey = ReadSpawnDataValue(spawnData, "survivalWaveReplicatorKey"),
					PopulationScore = ReadSpawnDataValue(spawnData, "populationScore"),
					DebugEnemyMode = ReadSpawnDataValue(spawnData, "debugEnemyMode")
				};
			}

			internal string ToSummary()
			{
				return $"groupType={GroupType}; spawnType={SpawnType}; persistentGameDataID={PersistentGameDataID}; persistentWavePopDataID={PersistentWavePopDataID}; survivalWaveReplicatorKey={SurvivalWaveReplicatorKey}; populationScore={PopulationScore}; debugEnemyMode={DebugEnemyMode}";
			}

			private static string ReadSpawnDataValue(object spawnData, string memberName)
			{
				object obj = TryGetMemberValue(spawnData, memberName) ?? TryGetMemberValue(spawnData, "m_" + memberName);
				if (obj != null)
				{
					return FormatValueSummary(obj);
				}
				return "<null>";
			}
		}

		private sealed class AgentCaptureRecord
		{
			internal readonly object Instance;

			internal string AgentKey { get; private set; }

			internal string GroupKey { get; private set; }

			internal string EnemyData { get; private set; }

			internal string EnemyDataID { get; private set; }

			internal string EnemyDataName { get; private set; }

			internal string EnemyDataInternalName { get; private set; }

			internal string EnemyDataPersistentID { get; private set; }

			internal string InstanceName { get; private set; }

			internal string PersistentGameDataID { get; private set; }

			internal string PersistentID { get; private set; }

			internal string PopulationID { get; private set; }

			internal string EnemyID { get; private set; }

			internal string Type { get; private set; }

			internal string Name { get; private set; }

			internal bool NeedsDelayedResolve { get; private set; }

			internal float ResolveAfterTime { get; private set; }

			private AgentCaptureRecord(object instance, string agentKey)
			{
				Instance = instance;
				AgentKey = agentKey;
			}

			internal static AgentCaptureRecord FromSetup(object instance, object spawnData, string agentKey)
			{
				AgentCaptureRecord agentCaptureRecord = new AgentCaptureRecord(instance, agentKey);
				agentCaptureRecord.ReadSetupSpawnDataFallback(spawnData);
				agentCaptureRecord.ResolveFromInstance();
				return agentCaptureRecord;
			}

			internal string ToSummary()
			{
				return $"agentKey={AgentKey}; groupKey={GroupKey}; enemyDataID={EnemyDataID}; enemyDataName={EnemyDataName}; enemyDataInternalName={EnemyDataInternalName}; enemyDataPersistentID={EnemyDataPersistentID}; instanceName={InstanceName}; spawnDataEnemyData={EnemyData}; persistentGameDataID={PersistentGameDataID}; persistentID={PersistentID}; populationID={PopulationID}; enemyID={EnemyID}; type={Type}; name={Name}";
			}

			internal void ResolveFromInstance()
			{
				EnemyDataID = FirstKnown(ReadInstanceValue("EnemyDataID"), ReadInstanceValue("enemyDataID"), EnemyDataID);
				object obj = TryGetMemberValue(Instance, "EnemyData") ?? TryGetMemberValue(Instance, "enemyData") ?? TryGetMemberValue(Instance, "m_enemyData");
				if (obj != null)
				{
					EnemyData = FirstKnown(FormatValueSummary(obj), EnemyData);
					EnemyDataName = FirstKnown(ReadObjectValue(obj, "name"), EnemyDataName);
					EnemyDataInternalName = FirstKnown(ReadObjectValue(obj, "internalName"), EnemyDataInternalName);
					EnemyDataPersistentID = FirstKnown(ReadObjectValue(obj, "persistentID"), EnemyDataPersistentID);
				}
				InstanceName = FirstKnown(TryGetUnityObjectName(Instance), ReadInstanceValue("name"), InstanceName);
				UpdateGroupKey();
				NeedsDelayedResolve = IsUnknown(EnemyDataID) && IsUnknown(EnemyDataPersistentID) && IsUnknown(EnemyDataName) && IsUnknown(EnemyDataInternalName);
				if (NeedsDelayedResolve && ResolveAfterTime <= 0f)
				{
					ResolveAfterTime = Time.time + 0.2f;
				}
			}

			private void ReadSetupSpawnDataFallback(object spawnData)
			{
				EnemyData = ReadSetupDataValue(spawnData, "enemyData");
				EnemyDataID = ReadSetupDataValue(spawnData, "enemyDataID");
				PersistentGameDataID = ReadSetupDataValue(spawnData, "persistentGameDataID");
				PersistentID = ReadSetupDataValue(spawnData, "persistentID");
				PopulationID = ReadSetupDataValue(spawnData, "populationID");
				EnemyID = ReadSetupDataValue(spawnData, "enemyID");
				Type = ReadSetupDataValue(spawnData, "type");
				Name = ReadSetupDataValue(spawnData, "name");
				UpdateGroupKey();
			}

			private void UpdateGroupKey()
			{
				string text = FirstKnown(EnemyDataID, EnemyDataPersistentID, PersistentGameDataID, PersistentID, EnemyID);
				string text2 = FirstKnown(EnemyDataName, EnemyDataInternalName, InstanceName, Name, Type);
				if (!IsUnknown(text) && !IsUnknown(text2))
				{
					GroupKey = text + " / " + text2;
					return;
				}
				GroupKey = FirstKnown(text, text2, EnemyData, AgentKey, "<unknown enemy>");
			}

			private string ReadInstanceValue(string memberName)
			{
				return ReadObjectValue(Instance, memberName);
			}

			private static string ReadObjectValue(object source, string memberName)
			{
				object obj = TryGetMemberValue(source, memberName) ?? TryGetMemberValue(source, "m_" + memberName);
				if (obj != null)
				{
					return FormatValueSummary(obj);
				}
				return "<null>";
			}

			private static string ReadSetupDataValue(object spawnData, string memberName)
			{
				if (spawnData == null)
				{
					return "<null>";
				}
				object obj = TryGetMemberValue(spawnData, memberName) ?? TryGetMemberValue(spawnData, "m_" + memberName);
				if (obj != null)
				{
					return FormatValueSummary(obj);
				}
				return "<null>";
			}

			internal static string FirstKnown(params string[] values)
			{
				foreach (string text in values)
				{
					if (!string.IsNullOrWhiteSpace(text) && text != "<null>")
					{
						return text;
					}
				}
				return "<unknown enemy>";
			}

			private static bool IsUnknown(string value)
			{
				if (!string.IsNullOrWhiteSpace(value) && !(value == "<null>"))
				{
					return value == "<unknown enemy>";
				}
				return true;
			}
		}

		private sealed class SourceDumpMember
		{
			internal string Name { get; }

			internal object Value { get; }

			internal SourceDumpMember(string name, object value)
			{
				Name = name;
				Value = value;
			}
		}

		private sealed class BloodDoorCandidateEvaluation
		{
			internal bool SurvivalWavePresent { get; set; }

			internal bool SettingsPresent { get; set; }

			internal string SettingsName { get; set; } = "<null>";

			internal string SettingsPersistentID { get; set; } = "<null>";

			internal string PopulationDataID { get; set; } = "<null>";

			internal string SpawnType { get; set; } = "<null>";

			internal string OverrideWaveSpawnType { get; set; } = "<null>";

			internal string WavePauseMin { get; set; } = "<null>";

			internal string WavePauseMax { get; set; } = "<null>";

			internal bool OverrideWaveSpawnTypeMatched { get; set; }

			internal bool SpawnTypeMatched { get; set; }

			internal bool WavePauseMinMatched { get; set; }

			internal bool WavePauseMaxMatched { get; set; }

			internal bool KeywordMatched { get; set; }

			internal bool CandidateMatched { get; set; }
		}

		private sealed class SpawnDataBloodDoorCandidateEvaluation
		{
			internal string GroupType { get; set; } = "<null>";

			internal string DebugEnemyMode { get; set; } = "<null>";

			internal string SpawnType { get; set; } = "<null>";

			internal string PersistentGameDataID { get; set; } = "<null>";

			internal string PersistentWavePopDataID { get; set; } = "<null>";

			internal string SurvivalWaveReplicatorKey { get; set; } = "<null>";

			internal string CourseNodeIDPlusOne { get; set; } = "<null>";

			internal string AllowedSpawnTypes { get; set; } = "<empty>";

			internal string ExcludedSpawnTypes { get; set; } = "<empty>";

			internal bool GroupTypeMatched { get; set; }

			internal bool SpawnTypeMatched { get; set; }

			internal bool SpawnTypeExcluded { get; set; }

			internal bool CandidateMatched { get; set; }
		}

		private sealed class SpawnDataRouteEvaluation
		{
			internal SpawnDataBloodDoorCandidateEvaluation Candidate { get; init; }

			internal SpawnSourceKind CandidateSourceKind { get; init; }

			internal SpawnSourceKind InheritedSessionSourceKind { get; init; }

			internal SpawnSourceKind FinalSourceKind { get; init; }

			internal bool ForcedNotBloodDoor { get; init; }

			internal bool RouteExclusive { get; init; }

			internal bool SkippedDynamicBecauseBloodDoorCandidate { get; init; }

			internal string CandidateReason { get; init; }

			internal string TargetTracker { get; init; }
		}

		private sealed class SpawnPatchState
		{
			internal SpawnDataRouteEvaluation SpawnDataRoute { get; set; }
		}

		private sealed class SpecialEnemyFallbackEvaluation
		{
			internal string SpawnDataMode { get; init; }

			internal string GroupReplicatorKey { get; init; }

			internal bool KeywordMatched { get; init; }

			internal bool AggressiveModeMatched { get; init; }

			internal bool FallbackEnabled { get; init; }

			internal bool FallbackMatched { get; init; }

			internal string TargetTracker { get; init; }

			internal string Decision { get; init; }

			internal bool ShouldLog { get; init; }
		}

		private sealed class SpawnTracker
		{
			private readonly Dictionary<string, TrackedAlarmEnemy> TrackedEnemies = new Dictionary<string, TrackedAlarmEnemy>();

			private readonly Dictionary<string, SpawnTypeEntry> TrackedTypeEntries = new Dictionary<string, SpawnTypeEntry>(StringComparer.Ordinal);

			internal string Name { get; }

			internal int TotalSpawned => TrackedEnemies.Count;

			internal int AliveCount => TrackedEnemies.Values.Count((TrackedAlarmEnemy enemy) => enemy.IsAlive);

			internal IEnumerable<TrackedAlarmEnemy> Enemies => TrackedEnemies.Values;

			internal IEnumerable<SpawnTypeEntry> TypeEntries => TrackedTypeEntries.Values;

			internal int TypeEntryCount => TrackedTypeEntries.Count;

			internal SpawnTracker(string name)
			{
				Name = name;
			}

			internal bool TrackEnemy(AgentCaptureRecord record, SpawnSource spawnSource)
			{
				if (record == null || string.IsNullOrWhiteSpace(record.AgentKey) || TrackedEnemies.ContainsKey(record.AgentKey))
				{
					return false;
				}
				TrackedAlarmEnemy trackedAlarmEnemy = TrackedAlarmEnemy.FromCaptureRecord(record, spawnSource);
				TrackedEnemies[record.AgentKey] = trackedAlarmEnemy;
				AddTrackedEnemyToTypeEntry(trackedAlarmEnemy, Time.time);
				return true;
			}

			internal bool TryMarkDead(string uniqueKey)
			{
				if (string.IsNullOrWhiteSpace(uniqueKey) || !TrackedEnemies.TryGetValue(uniqueKey, out var value) || !value.IsAlive)
				{
					return false;
				}
				value.IsAlive = false;
				if (TrackedTypeEntries.TryGetValue(value.TypeKey, out var value2))
				{
					int alive = value2.Alive;
					value2.Alive = Math.Max(0, value2.Alive - 1);
					if (alive > 0 && value2.Alive == 0 && !value2.ZeroAliveTimeValid)
					{
						value2.ZeroAliveTime = Time.time;
						value2.ZeroAliveTimeValid = true;
					}
				}
				return true;
			}

			internal bool TryMarkAlive(string uniqueKey)
			{
				if (string.IsNullOrWhiteSpace(uniqueKey) || !TrackedEnemies.TryGetValue(uniqueKey, out var value) || value.IsAlive)
				{
					return false;
				}
				value.IsAlive = true;
				if (TrackedTypeEntries.TryGetValue(value.TypeKey, out var value2))
				{
					value2.Alive++;
					value2.ZeroAliveTime = 0f;
					value2.ZeroAliveTimeValid = false;
				}
				return true;
			}

			internal void UpdateEnemy(AgentCaptureRecord record)
			{
				if (record != null && TrackedEnemies.TryGetValue(record.AgentKey, out var value))
				{
					string typeKey = value.TypeKey;
					bool isAlive = value.IsAlive;
					value.UpdateFromCaptureRecord(record);
					SpawnTypeEntry value2;
					if (!string.Equals(typeKey, value.TypeKey, StringComparison.Ordinal))
					{
						RemoveTrackedEnemyFromTypeEntry(typeKey, isAlive);
						AddTrackedEnemyToTypeEntry(value, Time.time);
					}
					else if (TrackedTypeEntries.TryGetValue(value.TypeKey, out value2))
					{
						value2.DisplayName = value.DisplayName;
					}
				}
			}

			internal IEnumerable<SpawnTypeEntry> GetPrunableTypeEntries(float now, float timeoutSeconds)
			{
				return TrackedTypeEntries.Values.Where((SpawnTypeEntry entry) => entry.Total > 0 && entry.Alive == 0 && entry.ZeroAliveTimeValid && now - entry.ZeroAliveTime >= timeoutSeconds);
			}

			internal string[] PruneTypeEntry(string typeKey)
			{
				if (string.IsNullOrWhiteSpace(typeKey) || !TrackedTypeEntries.TryGetValue(typeKey, out var value) || value.Total <= 0 || value.Alive != 0 || !value.ZeroAliveTimeValid)
				{
					return Array.Empty<string>();
				}
				string[] array = (from enemy in TrackedEnemies.Values
					where !enemy.IsAlive && string.Equals(enemy.TypeKey, typeKey, StringComparison.Ordinal)
					select enemy.UniqueKey).ToArray();
				string[] array2 = array;
				foreach (string key in array2)
				{
					TrackedEnemies.Remove(key);
				}
				TrackedTypeEntries.Remove(typeKey);
				return array;
			}

			internal void Clear()
			{
				TrackedEnemies.Clear();
				TrackedTypeEntries.Clear();
			}

			private void AddTrackedEnemyToTypeEntry(TrackedAlarmEnemy tracked, float now)
			{
				if (!TrackedTypeEntries.TryGetValue(tracked.TypeKey, out var value))
				{
					value = new SpawnTypeEntry
					{
						TypeKey = tracked.TypeKey,
						DisplayName = tracked.DisplayName
					};
					TrackedTypeEntries[tracked.TypeKey] = value;
				}
				value.DisplayName = tracked.DisplayName;
				value.Total++;
				if (tracked.IsAlive)
				{
					value.Alive++;
				}
				value.LastAddedTime = now;
				value.ZeroAliveTime = 0f;
				value.ZeroAliveTimeValid = false;
			}

			private void RemoveTrackedEnemyFromTypeEntry(string typeKey, bool wasAlive)
			{
				if (TrackedTypeEntries.TryGetValue(typeKey, out var value))
				{
					value.Total = Math.Max(0, value.Total - 1);
					if (wasAlive)
					{
						value.Alive = Math.Max(0, value.Alive - 1);
					}
					if (value.Total == 0)
					{
						TrackedTypeEntries.Remove(typeKey);
					}
					else if (value.Alive == 0 && !value.ZeroAliveTimeValid)
					{
						value.ZeroAliveTime = Time.time;
						value.ZeroAliveTimeValid = true;
					}
				}
			}
		}

		private sealed class SpawnTypeEntry
		{
			internal string TypeKey { get; set; }

			internal string DisplayName { get; set; }

			internal int Total { get; set; }

			internal int Alive { get; set; }

			internal float LastAddedTime { get; set; }

			internal float ZeroAliveTime { get; set; }

			internal bool ZeroAliveTimeValid { get; set; }
		}

		private sealed class TrackedAlarmEnemy
		{
			internal string UniqueKey { get; private set; }

			internal object Agent { get; private set; }

			internal string EnemyDataID { get; private set; }

			internal string EnemyDataName { get; private set; }

			internal string DisplayName { get; private set; }

			internal string TypeKey { get; private set; }

			internal SpawnSource SpawnSource { get; private set; }

			internal bool IsAlive { get; set; }

			internal static TrackedAlarmEnemy FromCaptureRecord(AgentCaptureRecord record, SpawnSource spawnSource)
			{
				TrackedAlarmEnemy trackedAlarmEnemy = new TrackedAlarmEnemy();
				trackedAlarmEnemy.UniqueKey = record.AgentKey;
				trackedAlarmEnemy.Agent = record.Instance;
				trackedAlarmEnemy.SpawnSource = spawnSource;
				trackedAlarmEnemy.IsAlive = true;
				trackedAlarmEnemy.UpdateFromCaptureRecord(record);
				return trackedAlarmEnemy;
			}

			internal void UpdateFromCaptureRecord(AgentCaptureRecord record)
			{
				if (record != null)
				{
					Agent = record.Instance ?? Agent;
					EnemyDataID = AgentCaptureRecord.FirstKnown(record.EnemyDataID, record.EnemyDataPersistentID, record.PersistentGameDataID, EnemyDataID);
					EnemyDataName = AgentCaptureRecord.FirstKnown(record.EnemyDataName, record.EnemyDataInternalName, record.InstanceName, record.Name, record.Type, EnemyDataName);
					TypeKey = BuildStableEnemyTypeKey(EnemyDataID, EnemyDataName, record.GroupKey, UniqueKey);
					DisplayName = FormatEnemyNameForHud(EnemyDataID, AgentCaptureRecord.FirstKnown(EnemyDataName, record.GroupKey, UniqueKey, "<unknown enemy>"));
				}
			}

			private static string BuildStableEnemyTypeKey(string enemyDataID, string enemyDataName, string groupKey, string uniqueKey)
			{
				string text = AgentCaptureRecord.FirstKnown(enemyDataID);
				string text2 = AgentCaptureRecord.FirstKnown(enemyDataName);
				if (text != "<unknown enemy>" || text2 != "<unknown enemy>")
				{
					return text + "|" + text2;
				}
				return AgentCaptureRecord.FirstKnown(groupKey, uniqueKey, "<unknown enemy type>");
			}
		}

		private static readonly bool DebugMode = false;

		private const int MaxSourceDumpMembers = 80;

		private const int MaxSpawnDataDumpLines = 80;

		private const int MaxNestedSpawnDataDumpMembers = 30;

		private static readonly string[] SourceTypeDiscoveryKeywords = new string[11]
		{
			"Blood", "Door", "Warden", "Alarm", "SurvivalWave", "Wave", "Population", "EnemyGroup", "Spawn", "Security",
			"ChainedPuzzle"
		};

		private static readonly string[] SurvivalWaveDumpKeywords = new string[16]
		{
			"Wave", "Type", "Population", "Settings", "Data", "Persistent", "ID", "Name", "Event", "Alarm",
			"Door", "Blood", "Area", "Zone", "Course", "Spawn"
		};

		private static readonly string[] CourseNodeDumpKeywords = new string[8] { "Dimension", "Layer", "Zone", "Area", "Node", "Course", "Local", "Position" };

		private static readonly string[] SpawnDataDumpKeywords = new string[20]
		{
			"group", "type", "spawn", "enemy", "data", "id", "persistent", "node", "course", "area",
			"zone", "position", "rotation", "hibernate", "event", "wave", "blood", "door", "debug", "mode"
		};

		private static readonly string[] NestedSpawnDataDumpKeywords = new string[10] { "EnemyGroup", "SpawnData", "CourseNode", "Area", "Zone", "GameData", "Blood", "Door", "Wave", "Event" };

		private const bool EnableSpawnDiagnostics = true;

		private const int MaxSpawnDiagnosticsPerWindow = 20;

		private const float SpawnDiagnosticWindowSeconds = 10f;

		private const float ScanDelaySeconds = 0.2f;

		private const float CaptureWindowSeconds = 3f;

		private const float AlarmSessionQuietSeconds = 8f;

		private const float AgentDataResolveDelaySeconds = 0.2f;

		private const float AliveRefreshIntervalSeconds = 0.25f;

		private const float ClientFallbackWarmupSeconds = 20f;

		private static readonly HashSet<string> KnownDynamicEnemyIDs = new HashSet<string>(StringComparer.Ordinal) { "27", "28", "33", "39", "40", "43" };

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

		private static readonly List<SpawnCaptureRecord> ActiveCaptureRecords = new List<SpawnCaptureRecord>();

		private static readonly List<AgentCaptureRecord> ActiveAgentCaptureRecords = new List<AgentCaptureRecord>();

		private static readonly SpawnTracker DynamicSpawnTracker = new SpawnTracker("Dynamic");

		private static readonly SpawnTracker BloodDoorSpawnTracker = new SpawnTracker("BloodDoor");

		private static readonly Dictionary<string, SpawnTracker> EnemyTrackerByKey = new Dictionary<string, SpawnTracker>();

		private static readonly HashSet<string> CapturedAgentKeys = new HashSet<string>();

		private static int SpawnDiagnosticsThisWindow;

		private static float SpawnDiagnosticWindowStart;

		private static bool IsCaptureWindowOpen;

		private static float CaptureEndTime;

		private static bool IsAlarmSessionOpen;

		private static bool IsSpawnDataBloodDoorRouteExclusive;

		private static float AlarmSessionEndTime;

		private static float NextAliveRefreshTime;

		private static float StartupTime = -1f;

		private static SpawnSource CurrentSessionSpawnSource = SpawnSource.UnknownDynamic;

		private static int NextFallbackAgentKey;

		private static Type EnemyAgentType;

		private static Type EnemyUpdateManagerType;

		private static MethodInfo FindObjectsOfTypeMethod;

		private static MethodInfo FindObjectsOfTypeAllMethod;

		private static MethodInfo GetInstanceIdMethod;

		private static PropertyInfo UnityObjectNameProperty;

		private static bool HasLoggedEnemyAgentShape;

		private static bool HasLoggedAliveProbeFailure;

		private static bool HasLoggedSpawnDiagnosticStack;

		private static bool HasRunSourceTypeDiscovery;

		private static int SourceInvestigationEventIndex;

		private static ConfigEntry<bool> DebugSourceModeConfig;

		private static ConfigEntry<bool> EnableBloodDoorCandidateClassificationConfig;

		private static ConfigEntry<string> BloodDoorSettingsNameKeywordsConfig;

		private static ConfigEntry<bool> EnableSpawnDataBloodDoorCandidateClassificationConfig;

		private static ConfigEntry<string> BloodDoorSpawnDataGroupTypesConfig;

		private static ConfigEntry<string> BloodDoorSpawnDataSpawnTypesConfig;

		private static ConfigEntry<string> ExcludedBloodDoorSpawnDataSpawnTypesConfig;

		private static ConfigEntry<bool> EnableEnemySetupProbeConfig;

		private static ConfigEntry<string> EnemySetupProbeNameKeywordsConfig;

		private static ConfigEntry<bool> EnableSpecialEnemyFallbackTrackingConfig;

		private static ConfigEntry<string> SpecialEnemyFallbackNameKeywordsConfig;

		private static ConfigEntry<string> SpecialEnemyFallbackTargetConfig;

		private static ConfigEntry<bool> SpecialEnemyFallbackRequireAgressiveModeConfig;

		private static ConfigEntry<bool> DynamicAutoHideEnabledConfig;

		private static ConfigEntry<float> DynamicAutoHideAfterSecondsConfig;

		internal static bool DebugModeEnabled => DebugMode;

		private static bool DebugSourceModeEnabled => DebugSourceModeConfig?.Value ?? false;

		private static bool BloodDoorCandidateClassificationEnabled => EnableBloodDoorCandidateClassificationConfig?.Value ?? false;

		private static bool SpawnDataBloodDoorCandidateClassificationEnabled => EnableSpawnDataBloodDoorCandidateClassificationConfig?.Value ?? false;

		private static bool EnemySetupProbeEnabled => EnableEnemySetupProbeConfig?.Value ?? false;

		private static bool SpecialEnemyFallbackTrackingEnabled => EnableSpecialEnemyFallbackTrackingConfig?.Value ?? false;

		private static bool DynamicAutoHideEnabled => DynamicAutoHideEnabledConfig?.Value ?? true;

		private static float DynamicAutoHideAfterSeconds => Math.Max(1f, DynamicAutoHideAfterSecondsConfig?.Value ?? 5f);

		internal static void ConfigureDynamicAutoHide(ConfigEntry<bool> dynamicAutoHideEnabled, ConfigEntry<float> dynamicAutoHideAfterSeconds)
		{
			DynamicAutoHideEnabledConfig = dynamicAutoHideEnabled;
			DynamicAutoHideAfterSecondsConfig = dynamicAutoHideAfterSeconds;
		}

		internal static void ConfigureSourceInvestigation(ConfigEntry<bool> debugSourceMode, ConfigEntry<bool> enableBloodDoorCandidateClassification, ConfigEntry<string> bloodDoorSettingsNameKeywords, ConfigEntry<bool> enableSpawnDataBloodDoorCandidateClassification, ConfigEntry<string> bloodDoorSpawnDataGroupTypes, ConfigEntry<string> bloodDoorSpawnDataSpawnTypes, ConfigEntry<string> excludedBloodDoorSpawnDataSpawnTypes, ConfigEntry<bool> enableEnemySetupProbe, ConfigEntry<string> enemySetupProbeNameKeywords, ConfigEntry<bool> enableSpecialEnemyFallbackTracking, ConfigEntry<string> specialEnemyFallbackNameKeywords, ConfigEntry<string> specialEnemyFallbackTarget, ConfigEntry<bool> specialEnemyFallbackRequireAgressiveMode)
		{
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006e: Expected O, but got Unknown
			DebugSourceModeConfig = debugSourceMode;
			EnableBloodDoorCandidateClassificationConfig = enableBloodDoorCandidateClassification;
			BloodDoorSettingsNameKeywordsConfig = bloodDoorSettingsNameKeywords;
			EnableSpawnDataBloodDoorCandidateClassificationConfig = enableSpawnDataBloodDoorCandidateClassification;
			BloodDoorSpawnDataGroupTypesConfig = bloodDoorSpawnDataGroupTypes;
			BloodDoorSpawnDataSpawnTypesConfig = bloodDoorSpawnDataSpawnTypes;
			ExcludedBloodDoorSpawnDataSpawnTypesConfig = excludedBloodDoorSpawnDataSpawnTypes;
			EnableEnemySetupProbeConfig = enableEnemySetupProbe;
			EnemySetupProbeNameKeywordsConfig = enemySetupProbeNameKeywords;
			EnableSpecialEnemyFallbackTrackingConfig = enableSpecialEnemyFallbackTracking;
			SpecialEnemyFallbackNameKeywordsConfig = specialEnemyFallbackNameKeywords;
			SpecialEnemyFallbackTargetConfig = specialEnemyFallbackTarget;
			SpecialEnemyFallbackRequireAgressiveModeConfig = specialEnemyFallbackRequireAgressiveMode;
			if (DebugSourceModeEnabled)
			{
				ManualLogSource logSource = Plugin.LogSource;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(122, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[AST SourceInvestigation] BloodDoor investigation build enabled. survivalWaveCandidateEnabled=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(BloodDoorCandidateClassificationEnabled);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnDataCandidateEnabled=");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(SpawnDataBloodDoorCandidateClassificationEnabled);
				}
				logSource.LogInfo(val);
			}
		}

		internal static void Apply(Harmony harmony)
		{
			//IL_031f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Expected O, but got Unknown
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_006f: Expected O, but got Unknown
			//IL_037d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0384: Expected O, but got Unknown
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Expected O, but got Unknown
			//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cd: Expected O, but got Unknown
			//IL_02cd: Expected O, but got Unknown
			//IL_02e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ec: Expected O, but got Unknown
			//IL_014e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Expected O, but got Unknown
			//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ec: Expected O, but got Unknown
			if (DebugMode)
			{
				Plugin.LogSource.LogInfo((object)"[Alarm Spawn Teller] Installing Enemies.EnemyGroup.Spawn patch...");
			}
			EnemyAgentType = typeof(EnemyAgent);
			EnemyUpdateManagerType = typeof(EnemyUpdateManager);
			RunSourceTypeDiscoveryOnce();
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val;
			if (EnemyAgentType == null)
			{
				Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find Enemies.EnemyAgent; spawn patch will still install, but enemy scan cannot run yet.");
			}
			else if (DebugMode)
			{
				ManualLogSource logSource = Plugin.LogSource;
				val = new BepInExInfoLogInterpolatedStringHandler(44, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found EnemyAgent type: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(EnemyAgentType.FullName);
				}
				logSource.LogInfo(val);
			}
			if (EnemyUpdateManagerType == null)
			{
				if (DebugMode)
				{
					Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find Enemies.EnemyUpdateManager; manager diagnostics unavailable.");
				}
			}
			else if (DebugMode)
			{
				ManualLogSource logSource2 = Plugin.LogSource;
				val = new BepInExInfoLogInterpolatedStringHandler(52, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found EnemyUpdateManager type: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(EnemyUpdateManagerType.FullName);
				}
				logSource2.LogInfo(val);
			}
			if (!InitializeUnityObjectAccess() && DebugMode)
			{
				Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Unity object reflection helpers unavailable; spawn patch will still install, but enemy scan/name fallback may be limited.");
			}
			Type typeFromHandle = typeof(EnemyGroup);
			if (typeFromHandle == null)
			{
				Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find Enemies.EnemyGroup.");
				return;
			}
			if (DebugMode)
			{
				ManualLogSource logSource3 = Plugin.LogSource;
				val = new BepInExInfoLogInterpolatedStringHandler(44, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found EnemyGroup type: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(typeFromHandle.FullName);
				}
				logSource3.LogInfo(val);
			}
			MethodInfo[] array = (from methodInfo4 in typeFromHandle.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				where methodInfo4.Name == "Spawn"
				select methodInfo4).ToArray();
			if (array.Length == 0)
			{
				Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find any Enemies.EnemyGroup.Spawn overloads.");
				LogSpawnLikeMethods(typeFromHandle);
				return;
			}
			MethodInfo[] array2;
			if (DebugMode)
			{
				array2 = array;
				foreach (MethodInfo method in array2)
				{
					ManualLogSource logSource4 = Plugin.LogSource;
					val = new BepInExInfoLogInterpolatedStringHandler(43, 1, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found Spawn overload: ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatMethodSignature(method));
					}
					logSource4.LogInfo(val);
				}
			}
			if (!array.Any(HasSurvivalWaveParameter))
			{
				Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find Enemies.EnemyGroup.Spawn overload with a SurvivalWave parameter.");
			}
			MethodInfo methodInfo = AccessTools.Method(typeof(EnemyGroupSpawnPatch), "Prefix", (Type[])null, (Type[])null);
			MethodInfo methodInfo2 = AccessTools.Method(typeof(EnemyGroupSpawnPatch), "Postfix", (Type[])null, (Type[])null);
			if (methodInfo == null || methodInfo2 == null)
			{
				Plugin.LogSource.LogError((object)"[Alarm Spawn Teller] Could not find local prefix/postfix methods for Harmony patch.");
				return;
			}
			int num2 = 0;
			array2 = array;
			foreach (MethodInfo methodInfo3 in array2)
			{
				try
				{
					harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(methodInfo), new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					num2++;
					if (DebugMode)
					{
						ManualLogSource logSource5 = Plugin.LogSource;
						val = new BepInExInfoLogInterpolatedStringHandler(72, 1, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Patched Spawn overload for diagnostics/statistics: ");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatMethodSignature(methodInfo3));
						}
						logSource5.LogInfo(val);
					}
				}
				catch (Exception ex)
				{
					ManualLogSource logSource6 = Plugin.LogSource;
					BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(54, 2, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[Alarm Spawn Teller] Failed to patch Spawn overload ");
						((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(FormatMethodSignature(methodInfo3));
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": ");
						((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex);
					}
					logSource6.LogError(val2);
				}
			}
			ManualLogSource logSource7 = Plugin.LogSource;
			val = new BepInExInfoLogInterpolatedStringHandler(57, 1, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Patched ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num2);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" EnemyGroup.Spawn overloads.");
			}
			logSource7.LogInfo(val);
			PatchEnemyAgentSetup(harmony);
			if (DebugMode)
			{
				SnapshotExistingEnemies("initial");
			}
		}

		private static void LogSpawnLikeMethods(Type enemyGroupType)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			MethodInfo[] array = (from methodInfo in enemyGroupType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				where methodInfo.Name.Contains("Spawn", StringComparison.OrdinalIgnoreCase)
				select methodInfo).ToArray();
			if (array.Length == 0)
			{
				Plugin.LogSource.LogWarning((object)"No method names containing Spawn were found on Enemies.EnemyGroup.");
				return;
			}
			MethodInfo[] array2 = array;
			bool flag = default(bool);
			foreach (MethodInfo method in array2)
			{
				ManualLogSource logSource = Plugin.LogSource;
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(44, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Found Enemies.EnemyGroup spawn-like method: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatMethodSignature(method));
				}
				logSource.LogInfo(val);
			}
		}

		private static void PatchEnemyAgentSetup(Harmony harmony)
		{
			//IL_0180: Unknown result type (might be due to invalid IL or missing references)
			//IL_0187: Expected O, but got Unknown
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Expected O, but got Unknown
			//IL_0123: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Expected O, but got Unknown
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Expected O, but got Unknown
			//IL_0146: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Expected O, but got Unknown
			if (EnemyAgentType == null)
			{
				Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Cannot patch EnemyAgent.Setup because Enemies.EnemyAgent was not found.");
				return;
			}
			MethodInfo[] array = (from methodInfo3 in EnemyAgentType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
				where methodInfo3.Name == "Setup"
				select methodInfo3).ToArray();
			if (array.Length == 0)
			{
				Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find any Enemies.EnemyAgent.Setup overloads.");
				return;
			}
			bool flag2 = default(bool);
			MethodInfo[] array2;
			BepInExInfoLogInterpolatedStringHandler val;
			if (DebugMode)
			{
				array2 = array;
				foreach (MethodInfo method in array2)
				{
					bool flag = HasEnemySpawnDataParameter(method);
					ManualLogSource logSource = Plugin.LogSource;
					val = new BepInExInfoLogInterpolatedStringHandler(76, 2, ref flag2);
					if (flag2)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found EnemyAgent.Setup overload: ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatMethodSignature(method));
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; has pEnemySpawnData=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(flag);
					}
					logSource.LogInfo(val);
				}
			}
			MethodInfo methodInfo = AccessTools.Method(typeof(EnemyGroupSpawnPatch), "EnemyAgentSetupPostfix", (Type[])null, (Type[])null);
			if (methodInfo == null)
			{
				Plugin.LogSource.LogError((object)"[Alarm Spawn Teller] Could not find local EnemyAgentSetupPostfix method.");
				return;
			}
			int num2 = 0;
			array2 = array;
			foreach (MethodInfo methodInfo2 in array2)
			{
				try
				{
					harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
					num2++;
					if (DebugMode)
					{
						ManualLogSource logSource2 = Plugin.LogSource;
						val = new BepInExInfoLogInterpolatedStringHandler(56, 1, ref flag2);
						if (flag2)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Patched EnemyAgent.Setup overload: ");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatMethodSignature(methodInfo2));
						}
						logSource2.LogInfo(val);
					}
				}
				catch (Exception ex)
				{
					ManualLogSource logSource3 = Plugin.LogSource;
					BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(65, 2, ref flag2);
					if (flag2)
					{
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[Alarm Spawn Teller] Failed to patch EnemyAgent.Setup overload ");
						((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(FormatMethodSignature(methodInfo2));
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": ");
						((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex);
					}
					logSource3.LogError(val2);
				}
			}
			ManualLogSource logSource4 = Plugin.LogSource;
			val = new BepInExInfoLogInterpolatedStringHandler(57, 1, ref flag2);
			if (flag2)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Patched ");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num2);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" EnemyAgent.Setup overloads.");
			}
			logSource4.LogInfo(val);
		}

		private static bool InitializeUnityObjectAccess()
		{
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			Type typeFromHandle = typeof(Object);
			if (DebugMode)
			{
				ManualLogSource logSource = Plugin.LogSource;
				bool flag = default(bool);
				BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(49, 2, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found Unity object type: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(typeFromHandle.FullName);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" [");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(typeFromHandle.Assembly.GetName().Name);
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("]");
				}
				logSource.LogInfo(val);
			}
			FindObjectsOfTypeMethod = typeFromHandle.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(delegate(MethodInfo method)
			{
				ParameterInfo[] parameters = method.GetParameters();
				return method.Name == "FindObjectsOfType" && parameters.Length == 1 && parameters[0].ParameterType == typeof(Type);
			});
			GetInstanceIdMethod = typeFromHandle.GetMethod("GetInstanceID", BindingFlags.Instance | BindingFlags.Public);
			UnityObjectNameProperty = typeFromHandle.GetProperty("name", BindingFlags.Instance | BindingFlags.Public);
			FindObjectsOfTypeAllMethod = typeof(Resources).GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(delegate(MethodInfo method)
			{
				ParameterInfo[] parameters = method.GetParameters();
				return method.Name == "FindObjectsOfTypeAll" && parameters.Length == 1 && parameters[0].ParameterType == typeof(Type);
			});
			if (FindObjectsOfTypeMethod == null || FindObjectsOfTypeAllMethod == null || GetInstanceIdMethod == null)
			{
				if (DebugMode)
				{
					Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not initialize Unity object reflection helpers.");
				}
				return false;
			}
			if (DebugMode)
			{
				Plugin.LogSource.LogInfo((object)"[Alarm Spawn Teller] Initialized Unity object reflection helpers.");
			}
			return true;
		}

		private static bool HasSurvivalWaveParameter(MethodBase method)
		{
			return method.GetParameters().Any(IsSurvivalWaveParameter);
		}

		private static bool HasEnemySpawnDataParameter(MethodBase method)
		{
			return method.GetParameters().Any(IsEnemySpawnDataParameter);
		}

		private static bool IsSurvivalWaveParameter(ParameterInfo parameter)
		{
			string? obj = parameter.ParameterType.FullName ?? parameter.ParameterType.Name;
			string text = parameter.Name ?? string.Empty;
			if (!obj.Contains("SurvivalWave", StringComparison.OrdinalIgnoreCase))
			{
				return text.Contains("survivalWave", StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static bool IsEnemySpawnDataParameter(ParameterInfo parameter)
		{
			if (!IsParameterNamedOrTyped(parameter, "pEnemySpawnData") && !IsParameterNamedOrTyped(parameter, "enemySpawnData"))
			{
				return IsParameterNamedOrTyped(parameter, "spawnData");
			}
			return true;
		}

		private static bool IsEnemyGroupSpawnDataParameter(ParameterInfo parameter)
		{
			return IsParameterNamedOrTyped(parameter, "pEnemyGroupSpawnData");
		}

		private static object GetSurvivalWaveArgument(MethodBase originalMethod, object[] args)
		{
			if (originalMethod == null || args == null)
			{
				return null;
			}
			ParameterInfo[] parameters = originalMethod.GetParameters();
			for (int i = 0; i < parameters.Length && i < args.Length; i++)
			{
				if (IsSurvivalWaveParameter(parameters[i]))
				{
					return args[i];
				}
			}
			return null;
		}

		private static bool IsAlarmWaveSpawn(MethodBase originalMethod, object[] args)
		{
			if (GetSurvivalWaveArgument(originalMethod, args) == null)
			{
				return false;
			}
			if (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "groupType"), out var _, out var value))
			{
				return string.Equals(value?.ToString(), "Survival", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static SpawnSource InferSpawnSource(MethodBase originalMethod, object[] args)
		{
			object survivalWaveArgument = GetSurvivalWaveArgument(originalMethod, args);
			if (survivalWaveArgument != null && BloodDoorCandidateClassificationEnabled && EvaluateBloodDoorCandidate(survivalWaveArgument).CandidateMatched)
			{
				return SpawnSource.BloodDoor;
			}
			if (SpawnDataBloodDoorCandidateClassificationEnabled && TryGetParameterValue(originalMethod, args, IsEnemyGroupSpawnDataParameter, out var _, out var value) && EvaluateSpawnDataBloodDoorCandidate(value).CandidateMatched)
			{
				return SpawnSource.BloodDoor;
			}
			if (IsAlarmWaveSpawn(originalMethod, args))
			{
				return SpawnSource.SurvivalWave;
			}
			if (survivalWaveArgument != null)
			{
				return SpawnSource.SurvivalWave;
			}
			return SpawnSource.UnknownDynamic;
		}

		private static string FormatSpawnSourceLabel(SpawnSource source)
		{
			return Localizer.GetSourceDisplayName(source);
		}

		private static bool TryGetSpawnDataArgument(MethodBase originalMethod, object[] args, out object spawnData)
		{
			ParameterInfo parameter;
			return TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "pEnemyGroupSpawnData") || IsParameterNamedOrTyped(parameter2, "spawnData"), out parameter, out spawnData);
		}

		private static bool TryGetEnemySpawnDataArgument(MethodBase originalMethod, object[] args, out object spawnData)
		{
			ParameterInfo parameter;
			return TryGetParameterValue(originalMethod, args, IsEnemySpawnDataParameter, out parameter, out spawnData);
		}

		private static bool TryGetParameterValue(MethodBase originalMethod, object[] args, Func<ParameterInfo, bool> predicate, out ParameterInfo parameter, out object value)
		{
			parameter = null;
			value = null;
			if (originalMethod == null || args == null)
			{
				return false;
			}
			ParameterInfo[] parameters = originalMethod.GetParameters();
			for (int i = 0; i < parameters.Length && i < args.Length; i++)
			{
				if (predicate(parameters[i]))
				{
					parameter = parameters[i];
					value = args[i];
					return true;
				}
			}
			return false;
		}

		private static bool IsParameterNamedOrTyped(ParameterInfo parameter, string keyword)
		{
			string? obj = parameter.ParameterType.FullName ?? parameter.ParameterType.Name;
			string text = parameter.Name ?? string.Empty;
			if (!obj.Contains(keyword, StringComparison.OrdinalIgnoreCase))
			{
				return text.Contains(keyword, StringComparison.OrdinalIgnoreCase);
			}
			return true;
		}

		private static void LogSpawnDiagnostic(MethodBase originalMethod, object[] args)
		{
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Expected O, but got Unknown
			//IL_028f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0296: Expected O, but got Unknown
			//IL_02d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d9: Expected O, but got Unknown
			if (!DebugMode || !CanLogSpawnDiagnostic())
			{
				return;
			}
			string text = ((originalMethod == null) ? "<unknown>" : FormatMethodSignature(originalMethod));
			ParameterInfo parameter;
			object value;
			string text2 = (TryGetParameterValue(originalMethod, args, IsSurvivalWaveParameter, out parameter, out value) ? $"present, null={value == null}" : "not present");
			object value2;
			string text3 = (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "spawnType"), out parameter, out value2) ? FormatValueSummary(value2) : "<not present>");
			object value3;
			string text4 = (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "groupType"), out parameter, out value3) ? FormatValueSummary(value3) : "<not present>");
			object value4;
			string text5 = (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "populationScore"), out parameter, out value4) ? FormatValueSummary(value4) : "<not present>");
			object value5;
			string text6 = (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "persistentGameDataID"), out parameter, out value5) ? FormatValueSummary(value5) : "<not present>");
			SpawnSource spawnSource = InferSpawnSource(originalMethod, args);
			ManualLogSource logSource = Plugin.LogSource;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(177, 9, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Spawn diagnostic: overload=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; survivalWave=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text2);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnType=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text3);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; groupType=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text4);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; populationScore=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text5);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; persistentGameDataID=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text6);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; activeSession=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(IsAlarmSessionOpen);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; activeCapture=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(IsCaptureWindowOpen);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; inferredSource=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSource>(spawnSource);
			}
			logSource.LogInfo(val);
			if (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "pEnemyGroupSpawnData") || IsParameterNamedOrTyped(parameter2, "spawnData"), out parameter, out var value6))
			{
				ManualLogSource logSource2 = Plugin.LogSource;
				val = new BepInExInfoLogInterpolatedStringHandler(49, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Spawn diagnostic spawnData: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatObjectMembersSummary(value6));
				}
				logSource2.LogInfo(val);
			}
			if (!HasLoggedSpawnDiagnosticStack)
			{
				HasLoggedSpawnDiagnosticStack = true;
				ManualLogSource logSource3 = Plugin.LogSource;
				val = new BepInExInfoLogInterpolatedStringHandler(45, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Spawn diagnostic stack: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Environment.StackTrace);
				}
				logSource3.LogInfo(val);
			}
		}

		private static void LogSourceInvestigation(MethodBase originalMethod, object[] args, SpawnPatchState patchState)
		{
			//IL_0432: Unknown result type (might be due to invalid IL or missing references)
			//IL_0439: Expected O, but got Unknown
			//IL_01e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e8: Expected O, but got Unknown
			//IL_0339: Unknown result type (might be due to invalid IL or missing references)
			//IL_0340: Expected O, but got Unknown
			if (!DebugSourceModeEnabled && !BloodDoorCandidateClassificationEnabled && !SpawnDataBloodDoorCandidateClassificationEnabled)
			{
				return;
			}
			int num = Interlocked.Increment(ref SourceInvestigationEventIndex);
			bool flag4 = default(bool);
			try
			{
				TryGetParameterValue(originalMethod, args, IsSurvivalWaveParameter, out var parameter, out var value);
				object value2;
				bool flag = TryGetParameterValue(originalMethod, args, IsEnemyGroupSpawnDataParameter, out parameter, out value2);
				if (value != null)
				{
					LogBloodDoorCandidate(num, value);
				}
				if (flag)
				{
					SpawnDataRouteEvaluation route = patchState?.SpawnDataRoute ?? ResolveSpawnDataRoute(value2);
					LogSpawnDataBloodDoorCandidate(num, route);
				}
				if (DebugSourceModeEnabled)
				{
					RunSourceTypeDiscoveryOnce();
					string text = ((originalMethod == null) ? "<unknown>" : FormatMethodSignature(originalMethod));
					string parameterSummary = GetParameterSummary(originalMethod, args, "groupType");
					string parameterSummary2 = GetParameterSummary(originalMethod, args, "spawnType");
					string parameterSummary3 = GetParameterSummary(originalMethod, args, "persistentGameDataID");
					string parameterSummary4 = GetParameterSummary(originalMethod, args, "populationScore");
					string parameterSummary5 = GetParameterSummary(originalMethod, args, "position");
					string parameterSummary6 = GetParameterSummary(originalMethod, args, "rotation");
					TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "courseNode"), out parameter, out var value3);
					TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "targetReplicator"), out parameter, out var value4);
					SpawnSource spawnSource = InferSpawnSource(originalMethod, args);
					SpawnSourceKind spawnSourceKind = GetSpawnSourceKind(CurrentSessionSpawnSource);
					SpawnTracker trackerForSource = GetTrackerForSource(CurrentSessionSpawnSource);
					SpawnDataRouteEvaluation obj = patchState?.SpawnDataRoute;
					bool flag2 = obj?.RouteExclusive ?? false;
					bool flag3 = obj?.SkippedDynamicBecauseBloodDoorCandidate ?? false;
					string text2 = obj?.TargetTracker ?? trackerForSource.Name;
					ManualLogSource logSource = Plugin.LogSource;
					BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(254, 14, ref flag4);
					if (flag4)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[AST SourceInvestigation] eventIndex=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; method=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; groupType=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnType=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary2);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; persistentGameDataID=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary3);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; populationScore=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary4);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; survivalWavePresent=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(value != null);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; survivalWaveType=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatRuntimeType(value));
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; position=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary5);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; rotation=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary6);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; courseNodePresent=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(value3 != null);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; courseNodeType=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatRuntimeType(value3));
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; targetReplicatorPresent=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(value4 != null);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; inferredSource=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSource>(spawnSource);
					}
					logSource.LogInfo(val);
					ManualLogSource logSource2 = Plugin.LogSource;
					val = new BepInExInfoLogInterpolatedStringHandler(173, 7, ref flag4);
					if (flag4)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[AST SpawnRoute] eventIndex=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; currentSessionSource=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSource>(CurrentSessionSpawnSource);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; currentSourceKind=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSourceKind>(spawnSourceKind);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; tracker=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text2);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; routeExclusive=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(flag2);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; skippedDynamicBecauseBloodDoorCandidate=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(flag3);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; bloodDoorClassificationEnabled=");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(BloodDoorCandidateClassificationEnabled);
					}
					logSource2.LogInfo(val);
					DumpFocusedMembers("[AST SurvivalWaveDump]", num, "survivalWave", value, (IReadOnlyCollection<string>)(object)SurvivalWaveDumpKeywords);
					DumpSurvivalWaveRelatedObjects(num, value);
					DumpFocusedMembers("[AST CourseNodeDump]", num, "courseNode", value3, (IReadOnlyCollection<string>)(object)CourseNodeDumpKeywords);
					if (flag)
					{
						DumpEnemyGroupSpawnData(num, value2);
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource logSource3 = Plugin.LogSource;
				BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(48, 3, ref flag4);
				if (flag4)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[AST SourceInvestigation] eventIndex=");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(num);
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("; failed=");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.GetType().Name);
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message);
				}
				logSource3.LogWarning(val2);
			}
		}

		private static void LogSpawnDataBloodDoorCandidate(int eventIndex, SpawnDataRouteEvaluation route)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			SpawnDataBloodDoorCandidateEvaluation candidate = route.Candidate;
			ManualLogSource logSource = Plugin.LogSource;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(444, 21, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[AST SpawnDataBloodDoorCandidate] eventIndex=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(eventIndex);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; groupType=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.GroupType);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; debugEnemyMode=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.DebugEnemyMode);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnType=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.SpawnType);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; persistentGameDataID=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.PersistentGameDataID);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; persistentWavePopDataID=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.PersistentWavePopDataID);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; survivalWaveReplicatorKey=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.SurvivalWaveReplicatorKey);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; courseNodeIDPlusOne=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.CourseNodeIDPlusOne);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; groupTypeMatched=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(candidate.GroupTypeMatched);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnTypeMatched=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(candidate.SpawnTypeMatched);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnTypeExcluded=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(candidate.SpawnTypeExcluded);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; allowedSpawnTypes=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.AllowedSpawnTypes);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; excludedSpawnTypes=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.ExcludedSpawnTypes);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; candidateMatched=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(candidate.CandidateMatched);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; candidateSourceKind=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSourceKind>(route.CandidateSourceKind);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; inheritedSourceKind=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSourceKind>(route.InheritedSessionSourceKind);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; forcedNotBloodDoor=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(route.ForcedNotBloodDoor);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; candidateReason=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(route.CandidateReason);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; finalSourceKind=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSourceKind>(route.FinalSourceKind);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; targetTracker=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(route.TargetTracker);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; classificationEnabled=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(SpawnDataBloodDoorCandidateClassificationEnabled);
			}
			logSource.LogInfo(val);
		}

		private static SpawnDataRouteEvaluation ResolveSpawnDataRoute(object spawnData)
		{
			SpawnDataBloodDoorCandidateEvaluation spawnDataBloodDoorCandidateEvaluation = EvaluateSpawnDataBloodDoorCandidate(spawnData);
			SpawnSourceKind spawnSourceKind = (IsAlarmSessionOpen ? GetSpawnSourceKind(CurrentSessionSpawnSource) : SpawnSourceKind.None);
			SpawnSourceKind candidateSourceKind = (spawnDataBloodDoorCandidateEvaluation.CandidateMatched ? SpawnSourceKind.BloodDoor : SpawnSourceKind.None);
			bool forcedNotBloodDoor = !spawnDataBloodDoorCandidateEvaluation.CandidateMatched;
			SpawnSourceKind spawnSourceKind2 = (spawnDataBloodDoorCandidateEvaluation.CandidateMatched ? SpawnSourceKind.BloodDoor : (spawnSourceKind switch
			{
				SpawnSourceKind.BloodDoor => SpawnSourceKind.Dynamic, 
				SpawnSourceKind.Dynamic => SpawnSourceKind.Dynamic, 
				_ => SpawnSourceKind.None, 
			}));
			SpawnDataRouteEvaluation spawnDataRouteEvaluation = new SpawnDataRouteEvaluation();
			spawnDataRouteEvaluation.Candidate = spawnDataBloodDoorCandidateEvaluation;
			spawnDataRouteEvaluation.CandidateSourceKind = candidateSourceKind;
			spawnDataRouteEvaluation.InheritedSessionSourceKind = spawnSourceKind;
			spawnDataRouteEvaluation.FinalSourceKind = spawnSourceKind2;
			spawnDataRouteEvaluation.ForcedNotBloodDoor = forcedNotBloodDoor;
			spawnDataRouteEvaluation.RouteExclusive = spawnDataBloodDoorCandidateEvaluation.CandidateMatched;
			spawnDataRouteEvaluation.SkippedDynamicBecauseBloodDoorCandidate = spawnDataBloodDoorCandidateEvaluation.CandidateMatched;
			spawnDataRouteEvaluation.CandidateReason = GetSpawnDataCandidateReason(spawnDataBloodDoorCandidateEvaluation);
			SpawnDataRouteEvaluation spawnDataRouteEvaluation2 = spawnDataRouteEvaluation;
			spawnDataRouteEvaluation2.TargetTracker = spawnSourceKind2 switch
			{
				SpawnSourceKind.BloodDoor => BloodDoorSpawnTracker.Name, 
				SpawnSourceKind.Dynamic => DynamicSpawnTracker.Name, 
				_ => "<none>", 
			};
			return spawnDataRouteEvaluation;
		}

		private static string GetSpawnDataCandidateReason(SpawnDataBloodDoorCandidateEvaluation candidate)
		{
			if (candidate == null)
			{
				return "candidate unavailable";
			}
			if (candidate.SpawnTypeExcluded)
			{
				return "spawn type excluded";
			}
			if (!SpawnDataBloodDoorCandidateClassificationEnabled)
			{
				return "classification disabled";
			}
			if (!candidate.GroupTypeMatched)
			{
				return "group type not matched";
			}
			if (!candidate.SpawnTypeMatched)
			{
				return "spawn type not allowed";
			}
			if (!candidate.CandidateMatched)
			{
				return "candidate conditions not met";
			}
			return "candidate matched";
		}

		private static void ApplySpawnDataRoute(SpawnDataRouteEvaluation route)
		{
			if (route != null)
			{
				IsSpawnDataBloodDoorRouteExclusive = route.Candidate.CandidateMatched;
				if (!route.Candidate.CandidateMatched && CurrentSessionSpawnSource == SpawnSource.BloodDoor)
				{
					CurrentSessionSpawnSource = SpawnSource.UnknownDynamic;
				}
			}
		}

		private static SpawnDataBloodDoorCandidateEvaluation EvaluateSpawnDataBloodDoorCandidate(object spawnData)
		{
			SpawnDataBloodDoorCandidateEvaluation result = new SpawnDataBloodDoorCandidateEvaluation();
			if (spawnData == null)
			{
				return result;
			}
			result.GroupType = FormatCandidateValue(GetFirstMemberValue(spawnData, "groupType", "GroupType", "m_groupType"));
			result.DebugEnemyMode = FormatCandidateValue(GetFirstMemberValue(spawnData, "debugEnemyMode", "DebugEnemyMode", "m_debugEnemyMode"));
			result.SpawnType = FormatCandidateValue(GetFirstMemberValue(spawnData, "spawnType", "SpawnType", "m_spawnType"));
			result.PersistentGameDataID = FormatCandidateValue(GetFirstMemberValue(spawnData, "persistentGameDataID", "PersistentGameDataID", "m_persistentGameDataID"));
			result.PersistentWavePopDataID = FormatCandidateValue(GetFirstMemberValue(spawnData, "persistentWavePopDataID", "PersistentWavePopDataID", "m_persistentWavePopDataID"));
			result.SurvivalWaveReplicatorKey = FormatCandidateValue(GetFirstMemberValue(spawnData, "survivalWaveReplicatorKey", "SurvivalWaveReplicatorKey", "m_survivalWaveReplicatorKey"));
			result.CourseNodeIDPlusOne = FormatCandidateValue(GetFirstMemberValue(spawnData, "courseNodeIDPlusOne", "CourseNodeIDPlusOne", "m_courseNodeIDPlusOne"));
			string[] configuredValues = GetConfiguredValues(BloodDoorSpawnDataGroupTypesConfig?.Value);
			string[] configuredValues2 = GetConfiguredValues(BloodDoorSpawnDataSpawnTypesConfig?.Value);
			string[] configuredValues3 = GetConfiguredValues(ExcludedBloodDoorSpawnDataSpawnTypesConfig?.Value);
			result.AllowedSpawnTypes = FormatConfiguredValues(configuredValues2);
			result.ExcludedSpawnTypes = FormatConfiguredValues(configuredValues3);
			result.GroupTypeMatched = configuredValues.Length != 0 && !string.IsNullOrWhiteSpace(result.GroupType) && result.GroupType != "<null>" && configuredValues.Any((string groupType) => string.Equals(groupType, result.GroupType, StringComparison.OrdinalIgnoreCase));
			result.SpawnTypeMatched = configuredValues2.Length != 0 && !string.IsNullOrWhiteSpace(result.SpawnType) && result.SpawnType != "<null>" && configuredValues2.Any((string spawnType) => string.Equals(spawnType, result.SpawnType, StringComparison.OrdinalIgnoreCase));
			result.SpawnTypeExcluded = configuredValues3.Length != 0 && !string.IsNullOrWhiteSpace(result.SpawnType) && result.SpawnType != "<null>" && configuredValues3.Any((string spawnType) => string.Equals(spawnType, result.SpawnType, StringComparison.OrdinalIgnoreCase));
			result.CandidateMatched = SpawnDataBloodDoorCandidateClassificationEnabled && result.GroupTypeMatched && result.SpawnTypeMatched && !result.SpawnTypeExcluded;
			return result;
		}

		private static string[] GetConfiguredValues(string configuredValues)
		{
			if (string.IsNullOrWhiteSpace(configuredValues))
			{
				return Array.Empty<string>();
			}
			return (from value in configuredValues.Split(',', StringSplitOptions.RemoveEmptyEntries)
				select value.Trim() into value
				where !string.IsNullOrWhiteSpace(value)
				select value).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray();
		}

		private static string FormatConfiguredValues(string[] configuredValues)
		{
			if (configuredValues != null && configuredValues.Length != 0)
			{
				return string.Join(",", configuredValues);
			}
			return "<empty>";
		}

		private static void LogBloodDoorCandidate(int eventIndex, object survivalWave)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			BloodDoorCandidateEvaluation bloodDoorCandidateEvaluation = EvaluateBloodDoorCandidate(survivalWave);
			SpawnSourceKind spawnSourceKind = ((bloodDoorCandidateEvaluation.CandidateMatched && BloodDoorCandidateClassificationEnabled) ? SpawnSourceKind.BloodDoor : GetSpawnSourceKind(CurrentSessionSpawnSource));
			ManualLogSource logSource = Plugin.LogSource;
			bool flag = default(bool);
			BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(239, 12, ref flag);
			if (flag)
			{
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[AST BloodDoorCandidate] eventIndex=");
				((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(eventIndex);
				((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; settings.name=");