Decompiled source of CoordinateTriggerEvents v1.1.0

CoordinateTriggerEvents_v1.1.0.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
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 AIGraph;
using Agents;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Core.Logging.Interpolation;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using ChainedPuzzles;
using GTFO.API;
using GameData;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Il2CppSystem.Collections.Generic;
using LevelGeneration;
using Localization;
using Microsoft.CodeAnalysis;
using Player;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")]
[assembly: AssemblyCompany("CoordinateTriggerEvents")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CoordinateTriggerEvents")]
[assembly: AssemblyTitle("CoordinateTriggerEvents")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

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

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

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace CoordinateTriggerEvents
{
	internal static class PluginInfo
	{
		public const string GUID = "com.gtfo.coordinatetriggerevents";

		public const string NAME = "CoordinateTriggerEvents";

		public const string VERSION = "1.1.0";
	}
	[BepInPlugin("com.gtfo.coordinatetriggerevents", "CoordinateTriggerEvents", "1.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BasePlugin
	{
		private Harmony? _harmony;

		public override void Load()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			Runtime.Log = ((BasePlugin)this).Log;
			ConfigManager.LoadOrCreate(((BasePlugin)this).Log, force: true);
			EventAPI.OnExpeditionStarted += Runtime.OnExpeditionStarted;
			_harmony = new Harmony("com.gtfo.coordinatetriggerevents");
			SafePatchAll(_harmony, ((BasePlugin)this).Log);
			Runtime.LogVerbose($"{"CoordinateTriggerEvents"} {"1.1.0"} loaded. Config roots={ConfigManager.ConfigPathSummary}");
		}

		private static void SafePatchAll(Harmony harmony, ManualLogSource log)
		{
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			int num = 0;
			int num2 = 0;
			bool flag = default(bool);
			try
			{
				Type[] types = Assembly.GetExecutingAssembly().GetTypes();
				foreach (Type type in types)
				{
					if (!type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Any())
					{
						continue;
					}
					try
					{
						harmony.CreateClassProcessor(type).Patch();
						num++;
					}
					catch (Exception ex)
					{
						num2++;
						BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(36, 3, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Optional Harmony patch skipped: ");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(type.FullName);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
						}
						log.LogWarning(val);
					}
				}
			}
			catch (Exception ex2)
			{
				BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(34, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("SafePatchAll failed unexpectedly: ");
					((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex2);
				}
				log.LogError(val2);
			}
			Runtime.LogVerbose($"Harmony patches applied. PatchedClasses={num}, SkippedClasses={num2}");
		}
	}
	internal sealed class ConfigDocument
	{
		public string FilePath = string.Empty;

		public bool Enabled = true;

		public List<JsonElement> MainLevelLayoutIDs = new List<JsonElement>();

		public DebugOptions Debug = new DebugOptions();

		public List<PositionTriggerRule> PositionTriggers = new List<PositionTriggerRule>();

		public List<ScanTriggerRule> ScanTriggers = new List<ScanTriggerRule>();

		public List<InteractTriggerRule> InteractTriggers = new List<InteractTriggerRule>();
	}
	internal sealed class ScanTriggerRule
	{
		public string ID { get; set; } = string.Empty;


		public bool Enabled { get; set; } = true;


		public int PuzzleOverrideIndex { get; set; } = -1;


		public string TriggerMode { get; set; } = "OnScanActivated";


		public bool UsePlayerCountEvents { get; set; }

		public List<JsonElement> OnePlayerEvents { get; set; } = new List<JsonElement>();


		public List<JsonElement> TwoPlayerEvents { get; set; } = new List<JsonElement>();


		public List<JsonElement> ThreePlayerEvents { get; set; } = new List<JsonElement>();


		public List<JsonElement> FourPlayerEvents { get; set; } = new List<JsonElement>();


		public float Cooldown { get; set; }

		public bool RequireInExpedition { get; set; } = true;


		public bool RequireAlivePlayers { get; set; } = true;


		public bool UseTriggerCycleEvents { get; set; }

		public int TriggerCycleCount { get; set; } = 3;


		public List<JsonElement> TriggerCycleEvents { get; set; } = new List<JsonElement>();


		public List<JsonElement> Events { get; set; } = new List<JsonElement>();


		public List<JsonElement> WardenEvents { get; set; } = new List<JsonElement>();

	}
	internal sealed class ScanRuntimeState
	{
		public int LastPlayerCount;

		public bool HasObserved;

		public bool Fired;

		public float LastFireTime = -999999f;

		public bool IsActive;

		public bool ActivatedThisCycle;

		public bool ActivationPlayerCountEventsFired;

		public bool HadPlayersInside;

		public bool ExitTriggeredThisCycle;

		public bool AllPlayersInsideNow;

		public bool AllPlayersEnteredThisCycle;

		public bool AllPlayersExitedRepeatActive;

		public int ActivationEdgeSequence;

		public int ExitEdgeSequence;

		public int AllPlayersEnterEdgeSequence;

		public int AllPlayersExitEdgeSequence;
	}
	internal sealed class InteractTriggerRule
	{
		public string ID { get; set; } = string.Empty;


		public bool Enabled { get; set; } = true;


		public string TargetType { get; set; } = "Any";


		public string TriggerMode { get; set; } = string.Empty;


		public float Cooldown { get; set; }

		public bool RequireInExpedition { get; set; } = true;


		public int Index { get; set; } = -1;


		public int InstanceID { get; set; } = -1;


		public int SyncID { get; set; } = -1;


		public int SerialNumber { get; set; } = -1;


		public string ItemKey { get; set; } = string.Empty;


		public string PublicName { get; set; } = string.Empty;


		public string TerminalSerial { get; set; } = string.Empty;


		public string WorldEventObjectFilter { get; set; } = string.Empty;


		public uint DataBlockID { get; set; }

		public uint ItemID { get; set; }

		public string InternalName { get; set; } = string.Empty;


		public PositionData? Position { get; set; }

		public float Radius { get; set; } = 2f;


		public bool UsePickupDropCycleEvents { get; set; }

		public int PickupDropCycleCount { get; set; } = 3;


		public List<JsonElement> PickupDropCycleEvents { get; set; } = new List<JsonElement>();


		public List<JsonElement> Events { get; set; } = new List<JsonElement>();


		public List<JsonElement> WardenEvents { get; set; } = new List<JsonElement>();

	}
	internal sealed class DebugOptions
	{
		public bool Enabled;

		public bool ShowScanMarkers = true;

		public bool ShowNames = true;

		public string MarkerColor = "#00BFFF";

		public string LabelColor = "#FFFFFF";

		public float MarkerAlpha = 0.35f;

		public float HeightOffset = 0.05f;

		public float MarkerHeight = 0.025f;

		public float LabelHeightOffset = 1f;

		public float RadiusScale = 1f;

		public float MinimumRadius = 0.5f;

		public float RefreshInterval = 1f;

		public bool DumpRuntimeIndexes;
	}
	internal sealed class PositionTriggerRule
	{
		public string ID { get; set; } = string.Empty;


		public bool Enabled { get; set; } = true;


		public PositionData? Position { get; set; }

		public string TriggerAreaMode { get; set; } = "Radius";


		public float Radius { get; set; } = 3f;


		public int LocalIndex { get; set; } = -1;


		public int Count { get; set; } = -1;


		public string Layer { get; set; } = string.Empty;


		public int DimensionIndex { get; set; } = -1;


		public string TriggerMode { get; set; } = "AnyPlayerEnter";


		public bool UsePlayerCountEvents { get; set; }

		public bool UseTriggerCycleEvents { get; set; }

		public int TriggerCycleCount { get; set; } = 3;


		public List<JsonElement> TriggerCycleEvents { get; set; } = new List<JsonElement>();


		public List<JsonElement> OnePlayerEvents { get; set; } = new List<JsonElement>();


		public List<JsonElement> TwoPlayerEvents { get; set; } = new List<JsonElement>();


		public List<JsonElement> ThreePlayerEvents { get; set; } = new List<JsonElement>();


		public List<JsonElement> FourPlayerEvents { get; set; } = new List<JsonElement>();


		public float Cooldown { get; set; }

		public bool RequireInExpedition { get; set; } = true;


		public bool RequireAlivePlayers { get; set; } = true;


		public bool IncludeBots { get; set; } = true;


		public bool DebugVisible { get; set; } = true;


		public string DebugColor { get; set; } = string.Empty;


		public List<JsonElement> Events { get; set; } = new List<JsonElement>();


		public List<JsonElement> WardenEvents { get; set; } = new List<JsonElement>();

	}
	internal sealed class PositionData
	{
		public float x { get; set; }

		public float y { get; set; }

		public float z { get; set; }

		public Vector3 ToVector3()
		{
			//IL_0012: Unknown result type (might be due to invalid IL or missing references)
			return new Vector3(x, y, z);
		}
	}
	internal sealed class TriggerState
	{
		public bool WasInside;

		public bool Fired;

		public float LastFireTime = -999999f;

		public int LastInsidePlayerCount;

		public readonly HashSet<int> FiredPlayerCounts = new HashSet<int>();

		public int CompletedCycles;
	}
	internal static class ConfigManager
	{
		internal const string ConfigFolderName = "CoordinateTriggerEvents";

		internal const string TemplateChineseFileName = "Template_CN.json";

		internal const string TemplateEnglishFileName = "Template_EN.json";

		internal static readonly List<ConfigDocument> Configs = new List<ConfigDocument>();

		private static readonly object LockObject = new object();

		private static string _pluginDir = string.Empty;

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

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

		private static readonly TimeSpan ReloadDebounceDelay = TimeSpan.FromMilliseconds(350.0);

		private static bool _reloadQueued;

		private static DateTime _reloadNotBeforeUtc = DateTime.MinValue;

		private static string _reloadReason = string.Empty;

		private const float ContinuousTriggerMinimumCooldown = 1f;

		internal static string ConfigPathSummary
		{
			get
			{
				lock (LockObject)
				{
					return (Configs.Count == 0) ? "<none>" : string.Join(" | ", Configs.Select((ConfigDocument c) => c.FilePath));
				}
			}
		}

		internal static void LoadOrCreate(ManualLogSource? log, bool force)
		{
			//IL_01b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b9: Expected O, but got Unknown
			lock (LockObject)
			{
				string pluginPath = Paths.PluginPath;
				_pluginDir = GetPluginDirectory();
				Directory.CreateDirectory(pluginPath);
				CleanupLegacyPluginLocalTemplates(log);
				EnsureTemplateConfigs(log);
				List<string> list = GetConfigSearchRoots(log).Where(Directory.Exists).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList();
				EnsureConfigWatchers(list, log);
				List<string> list2 = (from p in list.SelectMany((string root) => Directory.GetFiles(root, "*.json", SearchOption.AllDirectories))
					where IsCoordinateTriggerConfigPath(p)
					select p).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList();
				bool flag = RefreshFileSnapshot(list2);
				if (!(force || flag))
				{
					return;
				}
				Configs.Clear();
				bool flag2 = default(bool);
				foreach (string item in list2)
				{
					try
					{
						ConfigDocument configDocument = ParseConfig(item);
						if (configDocument != null)
						{
							Configs.Add(configDocument);
							Runtime.LogVerbose($"Loaded config: {item} | positionTriggers={configDocument.PositionTriggers.Count}, scanTriggers={configDocument.ScanTriggers.Count}, interactTriggers={configDocument.InteractTriggers.Count}");
							LogTriggerEnabledStates(log, configDocument);
						}
					}
					catch (Exception ex)
					{
						if (log != null)
						{
							BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(28, 3, ref flag2);
							if (flag2)
							{
								((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to load config '");
								((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(item);
								((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': ");
								((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name);
								((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
								((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
							}
							log.LogError(val);
						}
					}
				}
				Runtime.ClearConfigurationResolutionCaches();
				Runtime.MarkActiveTriggerCacheDirty();
			}
		}

		private static void LogTriggerEnabledStates(ManualLogSource? log, ConfigDocument doc)
		{
			foreach (PositionTriggerRule positionTrigger in doc.PositionTriggers)
			{
				Runtime.LogVerbose($"Loaded position trigger '{positionTrigger.ID}' Enabled={positionTrigger.Enabled}{(positionTrigger.Enabled ? string.Empty : " (skipped until enabled)")}");
			}
			foreach (ScanTriggerRule scanTrigger in doc.ScanTriggers)
			{
				Runtime.LogVerbose($"Loaded scan trigger '{scanTrigger.ID}' Enabled={scanTrigger.Enabled}{(scanTrigger.Enabled ? string.Empty : " (skipped until enabled)")}");
			}
			foreach (InteractTriggerRule interactTrigger in doc.InteractTriggers)
			{
				Runtime.LogVerbose($"Loaded interact trigger '{interactTrigger.ID}' Enabled={interactTrigger.Enabled}{(interactTrigger.Enabled ? string.Empty : " (skipped until enabled)")}");
			}
		}

		internal static void ProcessQueuedReload(ManualLogSource? log)
		{
			string reloadReason;
			lock (LockObject)
			{
				if (!_reloadQueued || DateTime.UtcNow < _reloadNotBeforeUtc)
				{
					return;
				}
				_reloadQueued = false;
				reloadReason = _reloadReason;
				_reloadReason = string.Empty;
			}
			Runtime.LogVerbose("Config file save detected; reloading CTE configs. Reason=" + reloadReason);
			LoadOrCreate(log, force: true);
		}

		private static void QueueReloadFromWatcher(string reason)
		{
			lock (LockObject)
			{
				_reloadQueued = true;
				_reloadNotBeforeUtc = DateTime.UtcNow + ReloadDebounceDelay;
				_reloadReason = reason;
			}
		}

		private static bool RefreshFileSnapshot(List<string> files)
		{
			bool result = files.Count != LastWriteTimes.Count;
			HashSet<string> hashSet = new HashSet<string>(files, StringComparer.OrdinalIgnoreCase);
			foreach (string file in files)
			{
				DateTime dateTime = SafeGetLastWriteTimeUtc(file);
				if (!LastWriteTimes.TryGetValue(file, out var value) || value != dateTime)
				{
					result = true;
					LastWriteTimes[file] = dateTime;
				}
			}
			foreach (string item in LastWriteTimes.Keys.ToList())
			{
				if (!hashSet.Contains(item))
				{
					result = true;
					LastWriteTimes.Remove(item);
				}
			}
			return result;
		}

		private static DateTime SafeGetLastWriteTimeUtc(string file)
		{
			try
			{
				return File.Exists(file) ? File.GetLastWriteTimeUtc(file) : DateTime.MinValue;
			}
			catch
			{
				return DateTime.MinValue;
			}
		}

		private static void EnsureConfigWatchers(List<string> customRoots, ManualLogSource? log)
		{
			//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_0205: Expected O, but got Unknown
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (string customRoot in customRoots)
			{
				string path = Path.Combine(customRoot, "CoordinateTriggerEvents");
				if (Directory.Exists(path))
				{
					hashSet.Add(Path.GetFullPath(path));
				}
			}
			foreach (string item in ConfigWatchers.Keys.ToList())
			{
				if (!hashSet.Contains(item))
				{
					try
					{
						ConfigWatchers[item].EnableRaisingEvents = false;
						ConfigWatchers[item].Dispose();
					}
					catch
					{
					}
					ConfigWatchers.Remove(item);
					Runtime.LogVerbose("Stopped CTE config watcher: " + item);
				}
			}
			bool flag = default(bool);
			foreach (string item2 in hashSet)
			{
				if (ConfigWatchers.ContainsKey(item2))
				{
					continue;
				}
				try
				{
					FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(item2, "*.json")
					{
						IncludeSubdirectories = true,
						NotifyFilter = (NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime)
					};
					fileSystemWatcher.Changed += OnConfigFileChanged;
					fileSystemWatcher.Created += OnConfigFileChanged;
					fileSystemWatcher.Deleted += OnConfigFileChanged;
					fileSystemWatcher.Renamed += OnConfigFileRenamed;
					fileSystemWatcher.Error += OnConfigWatcherError;
					fileSystemWatcher.EnableRaisingEvents = true;
					ConfigWatchers[item2] = fileSystemWatcher;
					Runtime.LogVerbose("Started CTE config watcher: " + item2);
				}
				catch (Exception ex)
				{
					if (log != null)
					{
						BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(45, 3, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to start CTE config watcher for '");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(item2);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': ");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
						}
						log.LogWarning(val);
					}
				}
			}
		}

		private static void OnConfigFileChanged(object sender, FileSystemEventArgs e)
		{
			if (IsCoordinateTriggerConfigPath(e.FullPath))
			{
				QueueReloadFromWatcher($"{e.ChangeType}: {e.FullPath}");
			}
		}

		private static void OnConfigFileRenamed(object sender, RenamedEventArgs e)
		{
			if (IsCoordinateTriggerConfigPath(e.FullPath) || IsCoordinateTriggerConfigPath(e.OldFullPath))
			{
				QueueReloadFromWatcher("Renamed: " + e.OldFullPath + " -> " + e.FullPath);
			}
		}

		private static void OnConfigWatcherError(object sender, ErrorEventArgs e)
		{
			QueueReloadFromWatcher("WatcherError: " + e.GetException().GetType().Name);
		}

		internal static bool ShouldDumpRuntimeIndexes()
		{
			lock (LockObject)
			{
				return Configs.Any((ConfigDocument c) => c.Enabled && c.Debug.Enabled && c.Debug.DumpRuntimeIndexes);
			}
		}

		private static void EnsureTemplateConfigs(ManualLogSource? log)
		{
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Expected O, but got Unknown
			try
			{
				if (!TryGetMtfoCustomPath(out string customPath) || string.IsNullOrWhiteSpace(customPath))
				{
					if (log != null)
					{
						log.LogWarning((object)"MTFO CustomPath is not available; CTE template configs were not generated. Load a custom rundown through MTFO first.");
					}
				}
				else
				{
					string text = Path.Combine(customPath, "CoordinateTriggerEvents");
					Directory.CreateDirectory(text);
					WriteTemplateIfMissing(Path.Combine(text, "Template_CN.json"), CreateChineseTemplateJson(), log);
					WriteTemplateIfMissing(Path.Combine(text, "Template_EN.json"), CreateEnglishTemplateJson(), log);
				}
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					bool flag = default(bool);
					BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(41, 2, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to ensure CTE template configs: ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
					}
					log.LogWarning(val);
				}
			}
		}

		private static IEnumerable<string> GetConfigSearchRoots(ManualLogSource? log)
		{
			if (TryGetMtfoCustomPath(out string customPath) && !string.IsNullOrWhiteSpace(customPath))
			{
				yield return customPath;
			}
			else if (log != null)
			{
				log.LogWarning((object)"MTFO CustomPath is not available; CTE will not read plugin-local Custom fallback configs to avoid duplicate configuration loading.");
			}
		}

		private static void CleanupLegacyPluginLocalTemplates(ManualLogSource? log)
		{
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Expected O, but got Unknown
			try
			{
				string text = Path.Combine(_pluginDir, "Custom", "CoordinateTriggerEvents");
				if (!Directory.Exists(text))
				{
					return;
				}
				string[] array = new string[2] { "Template_CN.json", "Template_EN.json" };
				foreach (string path in array)
				{
					string text2 = Path.Combine(text, path);
					if (File.Exists(text2))
					{
						File.Delete(text2);
						Runtime.LogVerbose("Deleted legacy plugin-local template config: " + text2);
					}
				}
				if (!Directory.EnumerateFileSystemEntries(text).Any())
				{
					Directory.Delete(text);
					string directoryName = Path.GetDirectoryName(text);
					if (Directory.Exists(directoryName) && !Directory.EnumerateFileSystemEntries(directoryName).Any())
					{
						Directory.Delete(directoryName);
					}
				}
			}
			catch (Exception ex)
			{
				if (log != null)
				{
					bool flag = default(bool);
					BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(53, 2, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to clean legacy plugin-local CTE templates: ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message);
					}
					log.LogWarning(val);
				}
			}
		}

		private static bool TryGetMtfoCustomPath(out string customPath)
		{
			customPath = string.Empty;
			try
			{
				if (!((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins.TryGetValue("com.dak.MTFO", out var value))
				{
					return false;
				}
				Assembly assembly = ((value == null) ? null : value.Instance?.GetType()?.Assembly);
				if (assembly == null)
				{
					return false;
				}
				Type type = assembly.GetTypes().FirstOrDefault((Type t) => t.Name == "ConfigManager");
				if (type == null)
				{
					return false;
				}
				FieldInfo field = type.GetField("CustomPath", BindingFlags.Static | BindingFlags.Public);
				FieldInfo field2 = type.GetField("HasCustomContent", BindingFlags.Static | BindingFlags.Public);
				if (field2 != null)
				{
					object value2 = field2.GetValue(null);
					if (value2 is bool && !(bool)value2)
					{
						return false;
					}
				}
				if (field?.GetValue(null) is string text && !string.IsNullOrWhiteSpace(text))
				{
					customPath = text;
					return true;
				}
			}
			catch
			{
			}
			return false;
		}

		private static void WriteTemplateIfMissing(string path, string content, ManualLogSource? log)
		{
			if (!File.Exists(path))
			{
				Directory.CreateDirectory(Path.GetDirectoryName(path));
				File.WriteAllText(path, content);
				Runtime.LogVerbose("Created template config: " + path);
			}
		}

		private static bool IsCoordinateTriggerConfigPath(string path)
		{
			string text = path.Replace('\\', '/');
			if (text.Contains("/Custom/CoordinateTriggerEvents/", StringComparison.OrdinalIgnoreCase))
			{
				return text.EndsWith(".json", StringComparison.OrdinalIgnoreCase);
			}
			return false;
		}

		private static string GetPluginDirectory()
		{
			try
			{
				string location = Assembly.GetExecutingAssembly().Location;
				if (!string.IsNullOrWhiteSpace(location))
				{
					string directoryName = Path.GetDirectoryName(location);
					if (!string.IsNullOrWhiteSpace(directoryName))
					{
						return directoryName;
					}
				}
			}
			catch
			{
			}
			return Path.Combine(Paths.PluginPath, "CoordinateTriggerEvents");
		}

		private static ConfigDocument? ParseConfig(string file)
		{
			using JsonDocument jsonDocument = JsonDocument.Parse(File.ReadAllText(file), new JsonDocumentOptions
			{
				AllowTrailingCommas = true,
				CommentHandling = JsonCommentHandling.Skip
			});
			JsonElement rootElement = jsonDocument.RootElement;
			ConfigDocument configDocument = new ConfigDocument
			{
				FilePath = file
			};
			if (rootElement.ValueKind == JsonValueKind.Array)
			{
				configDocument.MainLevelLayoutIDs = new List<JsonElement>();
				configDocument.PositionTriggers = ReadTriggerArray(rootElement);
				ValidateUniqueTriggerIDs(configDocument);
				return configDocument;
			}
			if (rootElement.ValueKind != JsonValueKind.Object)
			{
				return null;
			}
			configDocument.Enabled = GetBool(rootElement, "Enabled", defaultValue: true);
			if (rootElement.TryGetProperty("MainLevelLayoutIDs", out var value))
			{
				configDocument.MainLevelLayoutIDs = ReadSelectorList(value);
			}
			configDocument.Debug = ReadDebugOptions(rootElement);
			JsonElement value3;
			if (rootElement.TryGetProperty("PositionTriggers", out var value2) && value2.ValueKind == JsonValueKind.Array)
			{
				configDocument.PositionTriggers = ReadTriggerArray(value2);
			}
			else if (rootElement.TryGetProperty("Triggers", out value3) && value3.ValueKind == JsonValueKind.Array)
			{
				configDocument.PositionTriggers = ReadTriggerArray(value3);
			}
			if (rootElement.TryGetProperty("ScanTriggers", out var value4) && value4.ValueKind == JsonValueKind.Array)
			{
				configDocument.ScanTriggers = ReadScanTriggerArray(value4);
			}
			JsonElement value6;
			JsonElement value7;
			if (rootElement.TryGetProperty("InteractTriggers", out var value5) && value5.ValueKind == JsonValueKind.Array)
			{
				configDocument.InteractTriggers = ReadInteractTriggerArray(value5);
			}
			else if (rootElement.TryGetProperty("InteractionTriggers", out value6) && value6.ValueKind == JsonValueKind.Array)
			{
				configDocument.InteractTriggers = ReadInteractTriggerArray(value6);
			}
			else if (rootElement.TryGetProperty("ObjectTriggers", out value7) && value7.ValueKind == JsonValueKind.Array)
			{
				configDocument.InteractTriggers = ReadInteractTriggerArray(value7);
			}
			ValidateUniqueTriggerIDs(configDocument);
			return configDocument;
		}

		private static void ValidateUniqueTriggerIDs(ConfigDocument doc)
		{
			ConfigDocument doc2 = doc;
			HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (PositionTriggerRule positionTrigger in doc2.PositionTriggers)
			{
				Check("Position", positionTrigger.ID);
			}
			foreach (ScanTriggerRule scanTrigger in doc2.ScanTriggers)
			{
				Check("Scan", scanTrigger.ID);
			}
			foreach (InteractTriggerRule interactTrigger in doc2.InteractTriggers)
			{
				Check("Interact", interactTrigger.ID);
			}
			void Check(string category, string id)
			{
				//IL_0025: Unknown result type (might be due to invalid IL or missing references)
				//IL_002b: Expected O, but got Unknown
				if (!string.IsNullOrWhiteSpace(id) && !seen.Add(id))
				{
					ManualLogSource log = Runtime.Log;
					if (log != null)
					{
						bool flag = default(bool);
						BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(118, 3, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: duplicate trigger ID '");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(id);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'. Trigger IDs must be globally unique within loaded configs. Category=");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(category);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", File=");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(doc2.FilePath);
						}
						log.LogError(val);
					}
				}
			}
		}

		private static bool TryApplyCooldownPolicy(JsonElement element, string category, string triggerId, string triggerMode, bool isContinuous, out float cooldown)
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected O, but got Unknown
			cooldown = GetFloat(element, "Cooldown", 0f);
			if (!isContinuous)
			{
				if (cooldown < 0f)
				{
					cooldown = 0f;
				}
				return true;
			}
			bool flag = default(bool);
			if (!HasProperty(element, "Cooldown"))
			{
				ManualLogSource log = Runtime.Log;
				if (log != null)
				{
					BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(153, 4, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: continuous ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(category);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" trigger '");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(triggerId);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' uses TriggerMode='");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(triggerMode);
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' but is missing required Cooldown. Continuous triggers require Cooldown >= ");
						((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(1f, "0.0");
						((BepInExLogInterpolatedStringHandler)val).AppendLiteral(". Trigger skipped.");
					}
					log.LogError(val);
				}
				return false;
			}
			if (cooldown < 1f)
			{
				ManualLogSource log = Runtime.Log;
				if (log != null)
				{
					BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(136, 5, ref flag);
					if (flag)
					{
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("CTE config warning: continuous ");
						((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(category);
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" trigger '");
						((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(triggerId);
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' uses TriggerMode='");
						((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(triggerMode);
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' with Cooldown=");
						((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<float>(cooldown, "0.###");
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(". Clamped to ");
						((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<float>(1f, "0.0");
						((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" to match AWO StartEventLoop safety semantics.");
					}
					log.LogWarning(val2);
				}
				cooldown = 1f;
			}
			return true;
		}

		private static bool HasProperty(JsonElement obj, params string[] names)
		{
			if (obj.ValueKind != JsonValueKind.Object)
			{
				return false;
			}
			foreach (string propertyName in names)
			{
				if (obj.TryGetProperty(propertyName, out var _))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsContinuousPositionTriggerMode(string triggerMode)
		{
			string text = Runtime.NormalizePositionTriggerMode(triggerMode);
			if (!(text == "anyplayerinside"))
			{
				return text == "allplayersinside";
			}
			return true;
		}

		private static bool IsContinuousScanTriggerMode(string triggerMode)
		{
			string text = Runtime.NormalizeTriggerMode(triggerMode);
			if (!(text == "onallplayersinsidescan"))
			{
				return text == "onallplayersexitedscan";
			}
			return true;
		}

		private static bool IsContinuousInteractTriggerMode(string targetType, string triggerMode)
		{
			string text = Runtime.NormalizeTargetType(targetType);
			string text2 = Runtime.NormalizeInteractionTriggerMode(triggerMode);
			if (!(text == "bigpickup") || (!(text2 == "onbigpickupheld") && !(text2 == "onbigpickupplaced")))
			{
				if (text == "terminal")
				{
					if (!(text2 == "onterminalusing"))
					{
						return text2 == "onterminalexited";
					}
					return true;
				}
				return false;
			}
			return true;
		}

		private static List<InteractTriggerRule> ReadInteractTriggerArray(JsonElement array)
		{
			//IL_04a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_04a7: Expected O, but got Unknown
			List<InteractTriggerRule> list = new List<InteractTriggerRule>();
			bool flag = default(bool);
			foreach (JsonElement item in array.EnumerateArray())
			{
				if (item.ValueKind != JsonValueKind.Object)
				{
					continue;
				}
				InteractTriggerRule interactTriggerRule = new InteractTriggerRule
				{
					ID = GetString(item, "ID", GetString(item, "Id", GetString(item, "id", string.Empty))),
					Enabled = GetBool(item, "Enabled", defaultValue: true),
					TargetType = GetString(item, "TargetType", GetString(item, "Target", GetString(item, "ObjectType", "Any"))),
					TriggerMode = GetString(item, "TriggerMode", GetString(item, "Trigger", string.Empty)),
					Cooldown = GetFloat(item, "Cooldown", 0f),
					RequireInExpedition = GetBool(item, "RequireInExpedition", defaultValue: true),
					Index = GetInt(item, "Index", -1),
					InstanceID = GetInt(item, "InstanceID", GetInt(item, "ObjectInstanceID", -1)),
					SyncID = GetInt(item, "SyncID", GetInt(item, "SyncId", -1)),
					SerialNumber = GetInt(item, "SerialNumber", GetInt(item, "Serial", -1)),
					ItemKey = GetString(item, "ItemKey", GetString(item, "Key", string.Empty)),
					PublicName = GetString(item, "PublicName", GetString(item, "NameContains", string.Empty)),
					TerminalSerial = GetString(item, "TSL", GetString(item, "TerminalTSL", GetString(item, "TerminalTsl", GetString(item, "TerminalSelector", GetString(item, "TerminalSerial", GetString(item, "TerminalSerialNumber", GetString(item, "TerminalSerialText", GetString(item, "SerialText", GetString(item, "SerialLookup", GetString(item, "TerminalSerialLookup", string.Empty)))))))))),
					WorldEventObjectFilter = GetString(item, "WorldEventObjectFilter", GetString(item, "Filter", GetString(item, "ObjectFilter", string.Empty))),
					DataBlockID = GetUInt(item, "DataBlockID", GetUInt(item, "ItemDataBlockID", GetUInt(item, "ItemID", 0u))),
					ItemID = GetUInt(item, "ItemID", GetUInt(item, "PickupID", 0u)),
					InternalName = GetString(item, "InternalName", GetString(item, "PrefabName", string.Empty)),
					Radius = GetFloat(item, "Radius", 2f),
					UsePickupDropCycleEvents = GetBool(item, "UsePickupDropCycleEvents", GetBool(item, "UseBehaviorCycleEvents", GetBool(item, "UsePickupDropGroupEvents", defaultValue: false))),
					PickupDropCycleCount = Math.Max(1, GetInt(item, "PickupDropCycleCount", GetInt(item, "BehaviorCycleCount", GetInt(item, "ActionGroupCount", 3))))
				};
				if (item.TryGetProperty("Position", out var value) && value.ValueKind == JsonValueKind.Object)
				{
					interactTriggerRule.Position = new PositionData
					{
						x = GetFloat(value, "x", GetFloat(value, "X", 0f)),
						y = GetFloat(value, "y", GetFloat(value, "Y", 0f)),
						z = GetFloat(value, "z", GetFloat(value, "Z", 0f))
					};
				}
				if (item.TryGetProperty("Events", out var value2) && value2.ValueKind == JsonValueKind.Array)
				{
					interactTriggerRule.Events = (from e in value2.EnumerateArray()
						select e.Clone()).ToList();
				}
				if (item.TryGetProperty("WardenEvents", out var value3) && value3.ValueKind == JsonValueKind.Array)
				{
					interactTriggerRule.WardenEvents = (from e in value3.EnumerateArray()
						select e.Clone()).ToList();
				}
				interactTriggerRule.PickupDropCycleEvents = ReadFirstArrayProperty(item, "PickupDropCycleEvents", "PickupDropGroupEvents", "BehaviorCycleEvents", "ActionGroupEvents", "CycleEvents");
				float cooldown;
				if (string.IsNullOrWhiteSpace(interactTriggerRule.ID))
				{
					ManualLogSource log = Runtime.Log;
					if (log != null)
					{
						BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(87, 1, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: trigger is missing required ID. File may be skipped partly. Category=");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(interactTriggerRule.GetType().Name);
						}
						log.LogError(val);
					}
				}
				else if (TryApplyCooldownPolicy(item, "Interact", interactTriggerRule.ID, interactTriggerRule.TriggerMode, IsContinuousInteractTriggerMode(interactTriggerRule.TargetType, interactTriggerRule.TriggerMode), out cooldown))
				{
					interactTriggerRule.Cooldown = cooldown;
					list.Add(interactTriggerRule);
				}
			}
			return list;
		}

		private static List<ScanTriggerRule> ReadScanTriggerArray(JsonElement array)
		{
			//IL_039c: Unknown result type (might be due to invalid IL or missing references)
			//IL_03a3: Expected O, but got Unknown
			List<ScanTriggerRule> list = new List<ScanTriggerRule>();
			bool flag = default(bool);
			foreach (JsonElement item in array.EnumerateArray())
			{
				if (item.ValueKind != JsonValueKind.Object)
				{
					continue;
				}
				ScanTriggerRule scanTriggerRule = new ScanTriggerRule
				{
					ID = GetString(item, "ID", GetString(item, "Id", GetString(item, "id", string.Empty))),
					Enabled = GetBool(item, "Enabled", defaultValue: true),
					PuzzleOverrideIndex = GetInt(item, "Index", GetInt(item, "PuzzleOverrideIndex", GetInt(item, "PuzzleIndex", -1))),
					TriggerMode = GetString(item, "TriggerMode", GetString(item, "Trigger", "OnScanActivated")),
					UsePlayerCountEvents = GetBool(item, "UsePlayerCountEvents", GetBool(item, "EnablePlayerCountEvents", defaultValue: false)),
					Cooldown = GetFloat(item, "Cooldown", 0f),
					RequireInExpedition = GetBool(item, "RequireInExpedition", defaultValue: true),
					RequireAlivePlayers = GetBool(item, "RequireAlivePlayers", defaultValue: true),
					UseTriggerCycleEvents = GetBool(item, "UseTriggerCycleEvents", GetBool(item, "UseScanCycleEvents", GetBool(item, "UseTriggerGroupEvents", defaultValue: false))),
					TriggerCycleCount = Math.Max(1, GetInt(item, "TriggerCycleCount", GetInt(item, "ScanCycleCount", GetInt(item, "TriggerGroupCount", 3))))
				};
				if (item.TryGetProperty("Events", out var value) && value.ValueKind == JsonValueKind.Array)
				{
					scanTriggerRule.Events = (from e in value.EnumerateArray()
						select e.Clone()).ToList();
				}
				if (item.TryGetProperty("WardenEvents", out var value2) && value2.ValueKind == JsonValueKind.Array)
				{
					scanTriggerRule.WardenEvents = (from e in value2.EnumerateArray()
						select e.Clone()).ToList();
				}
				scanTriggerRule.OnePlayerEvents = ReadFirstArrayProperty(item, "OnePlayerEvents", "EventsOnOnePlayer", "OnePlayerWardenEvents", "WardenEventsOnOnePlayer", "Player1Events", "Events1P", "Events_1P");
				scanTriggerRule.TwoPlayerEvents = ReadFirstArrayProperty(item, "TwoPlayerEvents", "EventsOnTwoPlayers", "TwoPlayerWardenEvents", "WardenEventsOnTwoPlayers", "Player2Events", "Events2P", "Events_2P");
				scanTriggerRule.ThreePlayerEvents = ReadFirstArrayProperty(item, "ThreePlayerEvents", "EventsOnThreePlayers", "ThreePlayerWardenEvents", "WardenEventsOnThreePlayers", "Player3Events", "Events3P", "Events_3P");
				scanTriggerRule.FourPlayerEvents = ReadFirstArrayProperty(item, "FourPlayerEvents", "EventsOnFourPlayers", "FourPlayerWardenEvents", "WardenEventsOnFourPlayers", "Player4Events", "Events4P", "Events_4P");
				ReadPlayerCountEventsObject(item, scanTriggerRule);
				scanTriggerRule.TriggerCycleEvents = ReadFirstArrayProperty(item, "TriggerCycleEvents", "ScanCycleEvents", "TriggerGroupEvents", "CycleEvents");
				float cooldown;
				if (string.IsNullOrWhiteSpace(scanTriggerRule.ID))
				{
					ManualLogSource log = Runtime.Log;
					if (log != null)
					{
						BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(87, 1, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: trigger is missing required ID. File may be skipped partly. Category=");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(scanTriggerRule.GetType().Name);
						}
						log.LogError(val);
					}
				}
				else if (TryApplyCooldownPolicy(item, "Scan", scanTriggerRule.ID, scanTriggerRule.TriggerMode, IsContinuousScanTriggerMode(scanTriggerRule.TriggerMode), out cooldown))
				{
					scanTriggerRule.Cooldown = cooldown;
					list.Add(scanTriggerRule);
				}
			}
			return list;
		}

		private static List<PositionTriggerRule> ReadTriggerArray(JsonElement array)
		{
			//IL_05c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_05cd: Expected O, but got Unknown
			List<PositionTriggerRule> list = new List<PositionTriggerRule>();
			bool flag = default(bool);
			foreach (JsonElement item in array.EnumerateArray())
			{
				if (item.ValueKind != JsonValueKind.Object)
				{
					continue;
				}
				PositionTriggerRule positionTriggerRule = new PositionTriggerRule
				{
					ID = GetString(item, "ID", GetString(item, "Id", GetString(item, "id", string.Empty))),
					Enabled = GetBool(item, "Enabled", defaultValue: true),
					TriggerAreaMode = GetString(item, "TriggerAreaMode", GetString(item, "AreaMode", GetString(item, "Mode", "Radius"))),
					Radius = GetFloat(item, "Radius", 3f),
					LocalIndex = GetInt(item, "LocalIndex", GetInt(item, "ZoneLocalIndex", -1)),
					Count = GetInt(item, "Count", GetInt(item, "AreaIndex", -1)),
					Layer = GetString(item, "Layer", string.Empty),
					DimensionIndex = GetInt(item, "DimensionIndex", -1),
					TriggerMode = GetString(item, "TriggerMode", "AnyPlayerEnter"),
					UsePlayerCountEvents = GetBool(item, "UsePlayerCountEvents", GetBool(item, "EnablePlayerCountEvents", GetBool(item, "UsePlayerCountEventGroups", GetBool(item, "EnablePlayerCountEventGroups", defaultValue: false)))),
					UseTriggerCycleEvents = GetBool(item, "UseTriggerCycleEvents", GetBool(item, "UsePositionCycleEvents", GetBool(item, "UseTriggerGroupEvents", defaultValue: false))),
					TriggerCycleCount = Math.Max(1, GetInt(item, "TriggerCycleCount", GetInt(item, "PositionCycleCount", GetInt(item, "TriggerGroupCount", 3)))),
					Cooldown = GetFloat(item, "Cooldown", 0f),
					RequireInExpedition = GetBool(item, "RequireInExpedition", defaultValue: true),
					RequireAlivePlayers = GetBool(item, "RequireAlivePlayers", defaultValue: true),
					IncludeBots = GetBool(item, "IncludeBots", defaultValue: true),
					DebugVisible = GetBool(item, "DebugVisible", defaultValue: true),
					DebugColor = GetString(item, "DebugColor", string.Empty)
				};
				JsonElement value2;
				if (item.TryGetProperty("Position", out var value) && value.ValueKind == JsonValueKind.Object)
				{
					positionTriggerRule.Position = new PositionData
					{
						x = GetFloat(value, "x", GetFloat(value, "X", 0f)),
						y = GetFloat(value, "y", GetFloat(value, "Y", 0f)),
						z = GetFloat(value, "z", GetFloat(value, "Z", 0f))
					};
				}
				else if (item.TryGetProperty("x", out value2) || item.TryGetProperty("X", out value2) || item.TryGetProperty("y", out value2) || item.TryGetProperty("Y", out value2) || item.TryGetProperty("z", out value2) || item.TryGetProperty("Z", out value2))
				{
					positionTriggerRule.Position = new PositionData
					{
						x = GetFloat(item, "x", GetFloat(item, "X", 0f)),
						y = GetFloat(item, "y", GetFloat(item, "Y", 0f)),
						z = GetFloat(item, "z", GetFloat(item, "Z", 0f))
					};
				}
				if (item.TryGetProperty("Events", out var value3) && value3.ValueKind == JsonValueKind.Array)
				{
					positionTriggerRule.Events = (from e in value3.EnumerateArray()
						select e.Clone()).ToList();
				}
				if (item.TryGetProperty("WardenEvents", out var value4) && value4.ValueKind == JsonValueKind.Array)
				{
					positionTriggerRule.WardenEvents = (from e in value4.EnumerateArray()
						select e.Clone()).ToList();
				}
				positionTriggerRule.OnePlayerEvents = ReadFirstArrayProperty(item, "OnePlayerEvents", "EventsOnOnePlayer", "OnePlayerWardenEvents", "WardenEventsOnOnePlayer", "Player1Events", "Events1P", "Events_1P");
				positionTriggerRule.TwoPlayerEvents = ReadFirstArrayProperty(item, "TwoPlayerEvents", "EventsOnTwoPlayers", "TwoPlayerWardenEvents", "WardenEventsOnTwoPlayers", "Player2Events", "Events2P", "Events_2P");
				positionTriggerRule.ThreePlayerEvents = ReadFirstArrayProperty(item, "ThreePlayerEvents", "EventsOnThreePlayers", "ThreePlayerWardenEvents", "WardenEventsOnThreePlayers", "Player3Events", "Events3P", "Events_3P");
				positionTriggerRule.FourPlayerEvents = ReadFirstArrayProperty(item, "FourPlayerEvents", "EventsOnFourPlayers", "FourPlayerWardenEvents", "WardenEventsOnFourPlayers", "Player4Events", "Events4P", "Events_4P");
				ReadPlayerCountEventsObject(item, positionTriggerRule);
				positionTriggerRule.TriggerCycleEvents = ReadFirstArrayProperty(item, "TriggerCycleEvents", "PositionCycleEvents", "TriggerGroupEvents", "CycleEvents");
				float cooldown;
				if (string.IsNullOrWhiteSpace(positionTriggerRule.ID))
				{
					ManualLogSource log = Runtime.Log;
					if (log != null)
					{
						BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(87, 1, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: trigger is missing required ID. File may be skipped partly. Category=");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(positionTriggerRule.GetType().Name);
						}
						log.LogError(val);
					}
				}
				else if (TryApplyCooldownPolicy(item, "Position", positionTriggerRule.ID, positionTriggerRule.TriggerMode, IsContinuousPositionTriggerMode(positionTriggerRule.TriggerMode), out cooldown))
				{
					positionTriggerRule.Cooldown = cooldown;
					list.Add(positionTriggerRule);
				}
			}
			return list;
		}

		private static List<JsonElement> ReadFirstArrayProperty(JsonElement obj, params string[] names)
		{
			foreach (string propertyName in names)
			{
				if (obj.TryGetProperty(propertyName, out var value) && value.ValueKind == JsonValueKind.Array)
				{
					return (from e in value.EnumerateArray()
						select e.Clone()).ToList();
				}
			}
			return new List<JsonElement>();
		}

		private static void ReadPlayerCountEventsObject(JsonElement obj, PositionTriggerRule rule)
		{
			if (!obj.TryGetProperty("PlayerCountEvents", out var value) || value.ValueKind != JsonValueKind.Object)
			{
				return;
			}
			foreach (JsonProperty item in value.EnumerateObject())
			{
				if (item.Value.ValueKind == JsonValueKind.Array)
				{
					List<JsonElement> list = (from e in item.Value.EnumerateArray()
						select e.Clone()).ToList();
					switch (item.Name.Trim().ToLowerInvariant())
					{
					case "1":
					case "one":
					case "oneplayer":
						rule.OnePlayerEvents = list;
						break;
					case "2":
					case "two":
					case "twoplayers":
						rule.TwoPlayerEvents = list;
						break;
					case "3":
					case "three":
					case "threeplayers":
						rule.ThreePlayerEvents = list;
						break;
					case "4":
					case "four":
					case "fourplayers":
						rule.FourPlayerEvents = list;
						break;
					}
				}
			}
		}

		private static void ReadPlayerCountEventsObject(JsonElement obj, ScanTriggerRule rule)
		{
			if (!obj.TryGetProperty("PlayerCountEvents", out var value) || value.ValueKind != JsonValueKind.Object)
			{
				return;
			}
			foreach (JsonProperty item in value.EnumerateObject())
			{
				if (item.Value.ValueKind == JsonValueKind.Array)
				{
					List<JsonElement> list = (from e in item.Value.EnumerateArray()
						select e.Clone()).ToList();
					switch (item.Name.Trim().ToLowerInvariant())
					{
					case "1":
					case "one":
					case "oneplayer":
						rule.OnePlayerEvents = list;
						break;
					case "2":
					case "two":
					case "twoplayers":
						rule.TwoPlayerEvents = list;
						break;
					case "3":
					case "three":
					case "threeplayers":
						rule.ThreePlayerEvents = list;
						break;
					case "4":
					case "four":
					case "fourplayers":
						rule.FourPlayerEvents = list;
						break;
					}
				}
			}
		}

		private static List<JsonElement> ReadSelectorList(JsonElement value)
		{
			if (value.ValueKind == JsonValueKind.Array)
			{
				return (from e in value.EnumerateArray()
					select e.Clone()).ToList();
			}
			if (value.ValueKind == JsonValueKind.String || value.ValueKind == JsonValueKind.Number || value.ValueKind == JsonValueKind.Object)
			{
				return new List<JsonElement> { value.Clone() };
			}
			return new List<JsonElement>();
		}

		private static DebugOptions ReadDebugOptions(JsonElement root)
		{
			DebugOptions debugOptions = new DebugOptions();
			debugOptions.Enabled = GetBool(root, "EnableDebugScanMarkers", GetBool(root, "DebugScanMarkers", GetBool(root, "DebugEnabled", defaultValue: false)));
			debugOptions.ShowScanMarkers = GetBool(root, "ShowDebugScanMarkers", defaultValue: true);
			debugOptions.ShowNames = GetBool(root, "DebugShowNames", defaultValue: true);
			debugOptions.MarkerColor = GetString(root, "DebugMarkerColor", debugOptions.MarkerColor);
			debugOptions.LabelColor = GetString(root, "DebugLabelColor", debugOptions.LabelColor);
			debugOptions.MarkerAlpha = GetFloat(root, "DebugMarkerAlpha", debugOptions.MarkerAlpha);
			debugOptions.HeightOffset = GetFloat(root, "DebugHeightOffset", debugOptions.HeightOffset);
			debugOptions.MarkerHeight = GetFloat(root, "DebugMarkerHeight", debugOptions.MarkerHeight);
			debugOptions.LabelHeightOffset = GetFloat(root, "DebugLabelHeightOffset", debugOptions.LabelHeightOffset);
			debugOptions.RadiusScale = GetFloat(root, "DebugRadiusScale", debugOptions.RadiusScale);
			debugOptions.MinimumRadius = GetFloat(root, "DebugMinimumRadius", debugOptions.MinimumRadius);
			debugOptions.RefreshInterval = GetFloat(root, "DebugRefreshInterval", debugOptions.RefreshInterval);
			debugOptions.DumpRuntimeIndexes = GetBool(root, "DumpRuntimeIndexes", debugOptions.DumpRuntimeIndexes);
			if (root.TryGetProperty("Debug", out var value) && value.ValueKind == JsonValueKind.Object)
			{
				debugOptions.Enabled = GetBool(value, "Enabled", debugOptions.Enabled);
				debugOptions.ShowScanMarkers = GetBool(value, "ShowScanMarkers", debugOptions.ShowScanMarkers);
				debugOptions.ShowNames = GetBool(value, "ShowNames", debugOptions.ShowNames);
				debugOptions.MarkerColor = GetString(value, "MarkerColor", debugOptions.MarkerColor);
				debugOptions.LabelColor = GetString(value, "LabelColor", debugOptions.LabelColor);
				debugOptions.MarkerAlpha = GetFloat(value, "MarkerAlpha", debugOptions.MarkerAlpha);
				debugOptions.HeightOffset = GetFloat(value, "HeightOffset", debugOptions.HeightOffset);
				debugOptions.MarkerHeight = GetFloat(value, "MarkerHeight", debugOptions.MarkerHeight);
				debugOptions.LabelHeightOffset = GetFloat(value, "LabelHeightOffset", debugOptions.LabelHeightOffset);
				debugOptions.RadiusScale = GetFloat(value, "RadiusScale", debugOptions.RadiusScale);
				debugOptions.MinimumRadius = GetFloat(value, "MinimumRadius", debugOptions.MinimumRadius);
				debugOptions.RefreshInterval = GetFloat(value, "RefreshInterval", debugOptions.RefreshInterval);
				debugOptions.DumpRuntimeIndexes = GetBool(value, "DumpRuntimeIndexes", debugOptions.DumpRuntimeIndexes);
			}
			debugOptions.MarkerAlpha = Mathf.Clamp01(debugOptions.MarkerAlpha);
			debugOptions.RadiusScale = Math.Max(0.01f, debugOptions.RadiusScale);
			debugOptions.MinimumRadius = Math.Max(0.01f, debugOptions.MinimumRadius);
			debugOptions.MarkerHeight = Math.Max(0.001f, debugOptions.MarkerHeight);
			debugOptions.RefreshInterval = Math.Max(0.2f, debugOptions.RefreshInterval);
			return debugOptions;
		}

		private static bool GetBool(JsonElement obj, string name, bool defaultValue)
		{
			if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(name, out var value))
			{
				if (value.ValueKind == JsonValueKind.True)
				{
					return true;
				}
				if (value.ValueKind == JsonValueKind.False)
				{
					return false;
				}
				if (value.ValueKind == JsonValueKind.String && bool.TryParse(value.GetString(), out var result))
				{
					return result;
				}
			}
			return defaultValue;
		}

		private static float GetFloat(JsonElement obj, string name, float defaultValue)
		{
			if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(name, out var value))
			{
				if (value.ValueKind == JsonValueKind.Number && value.TryGetSingle(out var value2))
				{
					return value2;
				}
				if (value.ValueKind == JsonValueKind.String && float.TryParse(value.GetString(), out var result))
				{
					return result;
				}
			}
			return defaultValue;
		}

		private static int GetInt(JsonElement obj, string name, int defaultValue)
		{
			if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(name, out var value))
			{
				if (value.ValueKind == JsonValueKind.Number && value.TryGetInt32(out var value2))
				{
					return value2;
				}
				if (value.ValueKind == JsonValueKind.String && int.TryParse(value.GetString(), out var result))
				{
					return result;
				}
			}
			return defaultValue;
		}

		private static uint GetUInt(JsonElement obj, string name, uint defaultValue)
		{
			if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(name, out var value))
			{
				if (value.ValueKind == JsonValueKind.Number && value.TryGetUInt32(out var value2))
				{
					return value2;
				}
				if (value.ValueKind == JsonValueKind.String && uint.TryParse(value.GetString(), out var result))
				{
					return result;
				}
			}
			return defaultValue;
		}

		private static string GetString(JsonElement obj, string name, string defaultValue)
		{
			if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(name, out var value))
			{
				if (value.ValueKind == JsonValueKind.String)
				{
					return value.GetString() ?? defaultValue;
				}
				return value.ToString();
			}
			return defaultValue;
		}

		private static string CreateChineseTemplateJson()
		{
			return "// CoordinateTriggerEvents 1.1.0 中文配置模板\n// 生成位置:<自定义Rundown数据块文件夹>/Custom/CoordinateTriggerEvents/Template_CN.json\n// 模板默认 Enabled=false,不会影响关卡。复制本文件并改名为关卡配置后,把 Enabled 改为 true。\n// 插件允许 // 注释和尾随逗号。支持多个 JSON 文件;只会启用 MainLevelLayoutIDs 匹配当前关卡的配置。\n{\n  \"Enabled\": false,\n  \"MainLevelLayoutIDs\":0,\n  \"Debug\": {//调试模式,供模组开发者查看触发器的实际位置\n    \"Enabled\": false,\n    \"ShowScanMarkers\": true,\n    \"ShowNames\": true,\n    \"MarkerColor\": \"#00BFFF\",\n    \"LabelColor\": \"#FFFFFF\",\n    \"MarkerAlpha\": 0.35,\n    \"HeightOffset\": 0.05,\n    \"LabelHeightOffset\": 1.0\n  },\n  \"PositionTriggers\": [\n    {\n      \"ID\": \"radius_player_count_example\", //当前触发器的唯一id\n      \"TriggerAreaMode\": \"Radius\",\n      //Radius:按一个世界坐标点和半径生成触发范围。\n\n      \"TriggerMode\": \"AnyPlayerEnter\",\n      //AnyPlayerEnter  任意玩家进入触发范围触发事件\n\n      //AnyPlayerInside 触发范围内有任意玩家根据\"Cooldown\": 1.0,重复触发\n\n      //AllPlayersEnter 所有符合条件的玩家进入范围后触发\n\n      //AllPlayersInside 所有符合条件的玩家根据\"Cooldown\": 1.0,重复触发\n\n      //AnyPlayerExit 任意玩家从范围内离开范围时触发\n\n      //AllPlayersExit 所有玩家从范围内离开时触发\n\n      \"Enabled\": true,\n      \"Position\": {\n        \"x\": 0,\n        \"y\": 0,\n        \"z\": 0\n      },\n      \"Radius\": 5.0, //触发器大小\n      \"Cooldown\": 1.0, //内置冷却cd\n      \"RequireAlivePlayers\": true, //玩家倒地不计算为人数\n      \"DebugVisible\": true, //开启调试模式\n      \"Events\": [], //事件列表\n      \"UsePlayerCountEvents\": false,\n      // true 时只执行 PlayerCountEvents 中对应人数的事件组;false 时执行 Events内的事件组\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      // 每成功触发一次+1Count;累计 TriggerCycleCount 组后额外触发事件\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"whole_zone_localindex_0\",\n      \"TriggerAreaMode\": \"OverrideBigZone\",\n      //OverrideBigZone 生成一个覆盖指定ZONE的触发器\n      \"TriggerMode\": \"AnyPlayerEnter\",\n      \"Enabled\": true,\n      \"DimensionIndex\": 0,\n      \"Layer\": 0,\n      \"LocalIndex\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"DebugVisible\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"whole_area_count_0\",\n      \"TriggerAreaMode\": \"OverrideArea\",\n      //OverrideArea  生成一个覆盖指定Zone Area区域的触发器\n      \"TriggerMode\": \"AnyPlayerEnter\",\n      \"Enabled\": true,\n      \"DimensionIndex\": 0,\n      \"Layer\":0,\n      \"LocalIndex\": 0,\n      \"Count\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"DebugVisible\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    }\n  ],\n  \"ScanTriggers\": [\n    {\n      \"ID\": \"scan_1_activated_player_count\",\n      \"TriggerMode\": \"OnScanActivated\",\n      //OnScanActivated 玩家激活扫描点时触发事件\n      //OnPlayerExitScan 玩家退出扫描点时触发事件\n      \"Enabled\": true,\n      // Index 使用 ScanPositionOverride / ECC 在 BepInEx 日志中输出的 PuzzleOverrideIndex,索引从 1 开始。\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"scan_1_player_exit_event\",\n      \"TriggerMode\": \"OnPlayerExitScan\",\n      \"Enabled\": true,\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"scan_1_all_players_enter_once\",\n      \"TriggerMode\": \"OnAllPlayersEnterScan\",\n      \"Enabled\": true,\n      // 全员进入扫描点时触发一次。\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"scan_1_all_players_inside_repeat\",\n      \"TriggerMode\": \"OnAllPlayersInsideScan\",\n      \"Enabled\": true,\n      // 全员持续在扫描点内时按 Cooldown 重复触发。\n      \"Index\": 0,\n      \"Cooldown\": 5.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"scan_1_all_players_exit_once\",\n      \"TriggerMode\": \"OnAllPlayersExitScan\",\n      \"Enabled\": true,\n      // 全员曾进入扫描点后全部退出时触发一次。\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"scan_1_all_players_exited_repeat\",\n      \"TriggerMode\": \"OnAllPlayersExitedScan\",\n      \"Enabled\": true,\n      // 全员曾进入扫描点并全部退出后按 Cooldown 重复触发;初始无人不会触发。\n      \"Index\": 0,\n      \"Cooldown\": 5.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    }\n  ],\n  \"InteractTriggers\": [\n    {\n      \"ID\": \"bigpickup_1_pickup_event\",\n      \"TargetType\": \"BigPickup\",\n      \"TriggerMode\": \"OnBigPickupPickup\",\n      //OnBigPickupPickup 拾取大物品触发事件\n      //OnBigPickupDrop 放下大物品触发事件\n      \"Enabled\": true,\n      // Index 使用 ScanPositionOverride / ECC 在 BepInEx 日志中输出的 BigPickup Item Index,索引从 1 开始。\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"Events\": [],\n      \"UsePickupDropCycleEvents\": false,\n      \"PickupDropCycleCount\": 0,\n      \"PickupDropCycleEvents\": []\n    },\n    {\n      \"ID\": \"bigpickup_1_drop_event\",\n      \"TargetType\": \"BigPickup\",\n      \"TriggerMode\": \"OnBigPickupDrop\",\n      \"Enabled\": true,\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"bigpickup_1_held_repeat_event\",\n      \"TargetType\": \"BigPickup\",\n      \"TriggerMode\": \"OnBigPickupHeld\",\n      \"Enabled\": true,\n      \"Index\": 0,\n      // 玩家拿着该大物品期间按 Cooldown 重复触发,类似 AnyPlayerInside。\n      \"Cooldown\": 5.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"bigpickup_1_placed_repeat_event\",\n      \"TargetType\": \"BigPickup\",\n      \"TriggerMode\": \"OnBigPickupPlaced\",\n      \"Enabled\": true,\n      \"Index\": 0,\n      // 玩家放下该大物品后,只要大物品保持放置状态,就按 Cooldown 重复触发。\n      \"Cooldown\": 5.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"terminal_tsl_use_event\",\n      \"TargetType\": \"Terminal\",\n      \"TriggerMode\": \"OnTerminalUse\",\n      //OnTerminalUse 使用终端触发事件\n      //OnTerminalUnused 退出终端触发事件\n      \"Enabled\": true,\n      // TerminalSelector 与 AWO TSL 一致:[TERMINAL_DimensionIndex_LayerIndex_ZoneLocalIndex_TerminalIndexInZone]\n      \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n      \"Cooldown\": 1.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"terminal_tsl_unused_event\",\n      \"TargetType\": \"Terminal\",\n      \"TriggerMode\": \"OnTerminalUnused\",\n      \"Enabled\": true,\n      \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n      \"Cooldown\": 1.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"terminal_tsl_using_repeat_event\",\n      \"TargetType\": \"Terminal\",\n      \"TriggerMode\": \"OnTerminalUsing\",\n      \"Enabled\": true,\n      \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n      // 玩家正在使用该终端期间按 Cooldown 重复触发,类似 AnyPlayerInside。\n      \"Cooldown\": 5.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"terminal_tsl_exited_repeat_event\",\n      \"TargetType\": \"Terminal\",\n      \"TriggerMode\": \"OnTerminalExited\",\n      \"Enabled\": true,\n      \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n      // 必须玩家使用过一次该终端并退出后,才会按 Cooldown 重复触发;初始未使用状态不会触发。\n      \"Cooldown\": 5.0,\n      \"Events\": []\n    }\n  ]\n}";
		}

		private static string CreateEnglishTemplateJson()
		{
			return "// CoordinateTriggerEvents 1.1.0 English configuration template\n// Generated path: <Custom Rundown datablock folder>/Custom/CoordinateTriggerEvents/Template_EN.json\n// The template defaults to Enabled=false and will not affect levels. Copy this file, rename it as a level config, then set Enabled to true.\n// The plugin allows // comments and trailing commas. Multiple JSON files are supported; only configs whose MainLevelLayoutIDs match the current level are enabled.\n{\n  \"Enabled\": false,\n  \"MainLevelLayoutIDs\":0,\n  \"Debug\": {//Debug mode, for mod developers to view the actual positions of triggers\n    \"Enabled\": false,\n    \"ShowScanMarkers\": true,\n    \"ShowNames\": true,\n    \"MarkerColor\": \"#00BFFF\",\n    \"LabelColor\": \"#FFFFFF\",\n    \"MarkerAlpha\": 0.35,\n    \"HeightOffset\": 0.05,\n    \"LabelHeightOffset\": 1.0\n  },\n  \"PositionTriggers\": [\n    {\n      \"ID\": \"radius_player_count_example\", //Unique ID of the current trigger\n      \"TriggerAreaMode\": \"Radius\",\n      //Radius: generates a trigger area from a world position and radius.\n\n      \"TriggerMode\": \"AnyPlayerEnter\",\n      //AnyPlayerEnter  Triggers the event when any player enters the trigger area\n\n      //AnyPlayerInside Repeatedly triggers while any player is inside the trigger area according to \"Cooldown\": 1.0,\n\n      //AllPlayersEnter Triggers after all eligible players enter the area\n\n      //AllPlayersInside Repeatedly triggers while all eligible players are inside according to \"Cooldown\": 1.0,\n\n      //AnyPlayerExit Triggers when any player leaves the area from inside\n\n      //AllPlayersExit Triggers when all players leave the area from inside\n\n      \"Enabled\": true,\n      \"Position\": {\n        \"x\": 0,\n        \"y\": 0,\n        \"z\": 0\n      },\n      \"Radius\": 5.0, //Trigger size\n      \"Cooldown\": 1.0, //Built-in cooldown\n      \"RequireAlivePlayers\": true, //Downed players are not counted as player count\n      \"DebugVisible\": true, //Enable debug mode\n      \"Events\": [], //Event list\n      \"UsePlayerCountEvents\": false,\n      // When true, only executes the event group matching the player count in PlayerCountEvents; when false, executes the event group inside Events\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      // Adds 1 Count for every successful trigger; after accumulating TriggerCycleCount groups, triggers additional events\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"whole_zone_localindex_0\",\n      \"TriggerAreaMode\": \"OverrideBigZone\",\n      //OverrideBigZone generates a trigger that covers the specified ZONE\n      \"TriggerMode\": \"AnyPlayerEnter\",\n      \"Enabled\": true,\n      \"DimensionIndex\": 0,\n      \"Layer\": 0,\n      \"LocalIndex\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"DebugVisible\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"whole_area_count_0\",\n      \"TriggerAreaMode\": \"OverrideArea\",\n      //OverrideArea generates a trigger that covers the specified Zone Area\n      \"TriggerMode\": \"AnyPlayerEnter\",\n      \"Enabled\": true,\n      \"DimensionIndex\": 0,\n      \"Layer\":0,\n      \"LocalIndex\": 0,\n      \"Count\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"DebugVisible\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    }\n  ],\n  \"ScanTriggers\": [\n    {\n      \"ID\": \"scan_1_activated_player_count\",\n      \"TriggerMode\": \"OnScanActivated\",\n      //OnScanActivated Triggers the event when players activate the scan\n      //OnPlayerExitScan Triggers the event when a player exits the scan\n      \"Enabled\": true,\n      // Index uses the PuzzleOverrideIndex printed by ScanPositionOverride / ECC in the BepInEx log. The index starts from 1.\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"scan_1_player_exit_event\",\n      \"TriggerMode\": \"OnPlayerExitScan\",\n      \"Enabled\": true,\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"scan_1_all_players_enter_once\",\n      \"TriggerMode\": \"OnAllPlayersEnterScan\",\n      \"Enabled\": true,\n      // Triggers once when all players enter the scan.\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"scan_1_all_players_inside_repeat\",\n      \"TriggerMode\": \"OnAllPlayersInsideScan\",\n      \"Enabled\": true,\n      // Repeatedly triggers by Cooldown while all players continuously stay inside the scan.\n      \"Index\": 0,\n      \"Cooldown\": 5.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"scan_1_all_players_exit_once\",\n      \"TriggerMode\": \"OnAllPlayersExitScan\",\n      \"Enabled\": true,\n      // Triggers once after all players have entered the scan and then all leave it.\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    },\n    {\n      \"ID\": \"scan_1_all_players_exited_repeat\",\n      \"TriggerMode\": \"OnAllPlayersExitedScan\",\n      \"Enabled\": true,\n      // Repeatedly triggers by Cooldown after all players have entered the scan and then all leave it; it does not trigger when the scan starts empty.\n      \"Index\": 0,\n      \"Cooldown\": 5.0,\n      \"RequireAlivePlayers\": true,\n      \"Events\": [],\n      \"UsePlayerCountEvents\": false,\n      \"PlayerCountEvents\": {\n        \"1\": [],\n        \"2\": [],\n        \"3\": [],\n        \"4\": []\n      },\n      \"UseTriggerCycleEvents\": false,\n      \"TriggerCycleCount\": 0,\n      \"TriggerCycleEvents\": []\n    }\n  ],\n  \"InteractTriggers\": [\n    {\n      \"ID\": \"bigpickup_1_pickup_event\",\n      \"TargetType\": \"BigPickup\",\n      \"TriggerMode\": \"OnBigPickupPickup\",\n      //OnBigPickupPickup Triggers the event when a big pickup is picked up\n      //OnBigPickupDrop Triggers the event when a big pickup is dropped\n      \"Enabled\": true,\n      // Index uses the BigPickup Item Index printed by ScanPositionOverride / ECC in the BepInEx log. The index starts from 1.\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"Events\": [],\n      \"UsePickupDropCycleEvents\": false,\n      \"PickupDropCycleCount\": 0,\n      \"PickupDropCycleEvents\": []\n    },\n    {\n      \"ID\": \"bigpickup_1_drop_event\",\n      \"TargetType\": \"BigPickup\",\n      \"TriggerMode\": \"OnBigPickupDrop\",\n      \"Enabled\": true,\n      \"Index\": 0,\n      \"Cooldown\": 1.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"bigpickup_1_held_repeat_event\",\n      \"TargetType\": \"BigPickup\",\n      \"TriggerMode\": \"OnBigPickupHeld\",\n      \"Enabled\": true,\n      \"Index\": 0,\n      // Repeatedly triggers by Cooldown while the player is carrying this big pickup, similar to AnyPlayerInside.\n      \"Cooldown\": 5.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"bigpickup_1_placed_repeat_event\",\n      \"TargetType\": \"BigPickup\",\n      \"TriggerMode\": \"OnBigPickupPlaced\",\n      \"Enabled\": true,\n      \"Index\": 0,\n      // After the player drops this big pickup, repeatedly triggers by Cooldown while the big pickup remains placed.\n      \"Cooldown\": 5.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"terminal_tsl_use_event\",\n      \"TargetType\": \"Terminal\",\n      \"TriggerMode\": \"OnTerminalUse\",\n      //OnTerminalUse Triggers the event when using a terminal\n      //OnTerminalUnused Triggers the event when leaving a terminal\n      \"Enabled\": true,\n      // TerminalSelector is the same as AWO TSL: [TERMINAL_DimensionIndex_LayerIndex_ZoneLocalIndex_TerminalIndexInZone]\n      \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n      \"Cooldown\": 1.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"terminal_tsl_unused_event\",\n      \"TargetType\": \"Terminal\",\n      \"TriggerMode\": \"OnTerminalUnused\",\n      \"Enabled\": true,\n      \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n      \"Cooldown\": 1.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"terminal_tsl_using_repeat_event\",\n      \"TargetType\": \"Terminal\",\n      \"TriggerMode\": \"OnTerminalUsing\",\n      \"Enabled\": true,\n      \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n      // Repeatedly triggers by Cooldown while the player is using this terminal, similar to AnyPlayerInside.\n      \"Cooldown\": 5.0,\n      \"Events\": []\n    },\n    {\n      \"ID\": \"terminal_tsl_exited_repeat_event\",\n      \"TargetType\": \"Terminal\",\n      \"TriggerMode\": \"OnTerminalExited\",\n      \"Enabled\": true,\n      \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n      // The player must have used and then exited this terminal once before this repeatedly triggers by Cooldown; it will not trigger in the initial unused state.\n      \"Cooldown\": 5.0,\n      \"Events\": []\n    }\n  ]\n}";
		}
	}
	internal static class Runtime
	{
		private sealed class ZoneLookupCacheEntry
		{
			public readonly List<LG_Zone> Zones = new List<LG_Zone>();

			public readonly List<(LG_Zone Zone, object Area, int Index)> Areas = new List<(LG_Zone, object, int)>();

			public bool Resolved;

			public float LastAttemptTime = -999999f;

			public string LastFailure = string.Empty;
		}

		private sealed class PendingConfiguredEvent
		{
			public JsonElement EventElement;

			public string OwnerLabel = string.Empty;

			public float DueTime;
		}

		private readonly struct TerminalTslAddress
		{
			public readonly int DimensionIndex;

			public readonly int LayerIndex;

			public readonly int ZoneLocalIndex;

			public readonly int TerminalIndexInZone;

			public TerminalTslAddress(int dimensionIndex, int layerIndex, int zoneLocalIndex, int terminalIndexInZone)
			{
				DimensionIndex = dimensionIndex;
				LayerIndex = layerIndex;
				ZoneLocalIndex = zoneLocalIndex;
				TerminalIndexInZone = terminalIndexInZone;
			}

			public string ToToken()
			{
				return $"TERMINAL_{DimensionIndex}_{LayerIndex}_{ZoneLocalIndex}_{TerminalIndexInZone}";
			}

			public string ToBracketedToken()
			{
				return "[" + ToToken() + "]";
			}

			public bool Matches(TerminalTslAddress other)
			{
				if (DimensionIndex == other.DimensionIndex && LayerIndex == other.LayerIndex && ZoneLocalIndex == other.ZoneLocalIndex)
				{
					return TerminalIndexInZone == other.TerminalIndexInZone;
				}
				return false;
			}
		}

		internal static ManualLogSource? Log;

		internal static readonly bool VerboseConsoleLogging = false;

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

		private static readonly List<LocalizedText> LocalizedTextRoots = new List<LocalizedText>();

		private static float _lastTickTime;

		private static float _lastLogTime;

		private static readonly Dictionary<string, float> LastLogTimesByMessage = new Dictionary<string, float>(StringComparer.Ordinal);

		private static readonly Dictionary<string, string> PositionModeNormalizationCache = new Dictionary<string, string>(StringComparer.Ordinal);

		private static readonly Dictionary<string, string> ScanModeNormalizationCache = new Dictionary<string, string>(StringComparer.Ordinal);

		private static readonly Dictionary<string, string> InteractModeNormalizationCache = new Dictionary<string, string>(StringComparer.Ordinal);

		private static readonly Dictionary<string, string> TargetTypeNormalizationCache = new Dictionary<string, string>(StringComparer.Ordinal);

		private const int NormalizationCacheLimit = 512;

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

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

		private static Dictionary<string, uint>? PersistentIdDumpCache;

		private static readonly Dictionary<int, HashSet<string>> TerminalSelectorCache = new Dictionary<int, HashSet<string>>();

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

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

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

		private static bool TerminalSelectorCacheWarmupComplete;

		private static float LastTerminalSelectorCacheWarmupAttempt = -999999f;

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

		private static readonly object ActiveTriggerCacheLock = new object();

		private static readonly List<(ConfigDocument Config, PositionTriggerRule Trigger)> ActivePositionTriggerCache = new List<(ConfigDocument, PositionTriggerRule)>();

		private static readonly List<(ConfigDocument Config, ScanTriggerRule Trigger)> ActiveScanTriggerCache = new List<(ConfigDocument, ScanTriggerRule)>();

		private static readonly List<(ConfigDocument Config, InteractTriggerRule Trigger)> ActiveInteractTriggerCache = new List<(ConfigDocument, InteractTriggerRule)>();

		private static bool ActiveTriggerCacheDirty = true;

		private static uint ActiveTriggerCacheLayoutId = uint.MaxValue;

		private static string ActiveTriggerCacheLayoutName = string.Empty;

		private static readonly Queue<PendingConfiguredEvent> PendingConfiguredEvents = new Queue<PendingConfiguredEvent>();

		private const int MaxQueuedConfiguredEventsPerTick = 3;

		internal static void LogVerbose(string message)
		{
			if (VerboseConsoleLogging)
			{
				ManualLogSource? log = Log;
				if (log != null)
				{
					log.LogInfo((object)message);
				}
			}
		}

		internal static void MarkActiveTriggerCacheDirty()
		{
			lock (ActiveTriggerCacheLock)
			{
				ActiveTriggerCacheDirty = true;
			}
		}

		internal static void ClearConfigurationResolutionCaches()
		{
			PartialDataPersistentIdCache.Clear();
			LevelLayoutStringIdCache.Clear();
			PersistentIdDumpCache = null;
		}

		private static string GetCachedNormalization(Dictionary<string, string> cache, string? text, Func<string, string> normalize)
		{
			string text2 = text ?? string.Empty;
			if (cache.TryGetValue(text2, out string value) && value != null)
			{
				return value;
			}
			string text3 = normalize(text2);
			if (cache.Count < 512)
			{
				cache[text2] = text3;
			}
			return text3;
		}

		private static void ClearHotPathRuntimeCaches()
		{
			if (PositionModeNormalizationCache.Count > 512)
			{
				PositionModeNormalizationCache.Clear();
			}
			if (ScanModeNormalizationCache.Count > 512)
			{
				ScanModeNormalizationCache.Clear();
			}
			if (InteractModeNormalizationCache.Count > 512)
			{
				InteractModeNormalizationCache.Clear();
			}
			if (TargetTypeNormalizationCache.Count > 512)
			{
				TargetTypeNormalizationCache.Clear();
			}
		}

		private static void EnsureActiveTriggerCache()
		{
			uint currentLevelLayoutId = GetCurrentLevelLayoutId();
			string text = TryGetLevelLayoutName(currentLevelLayoutId);
			lock (ActiveTriggerCacheLock)
			{
				if (!ActiveTriggerCacheDirty && ActiveTriggerCacheLayoutId == currentLevelLayoutId && string.Equals(ActiveTriggerCacheLayoutName, text, StringComparison.OrdinalIgnoreCase))
				{
					return;
				}
				ActivePositionTriggerCache.Clear();
				ActiveScanTriggerCache.Clear();
				ActiveInteractTriggerCache.Clear();
				foreach (ConfigDocument config in ConfigManager.Configs)
				{
					if (!config.Enabled || !MatchesCurrentLevel(config, currentLevelLayoutId, text, out string _))
					{
						continue;
					}
					foreach (PositionTriggerRule positionTrigger in config.PositionTriggers)
					{
						if (positionTrigger.Enabled)
						{
							ActivePositionTriggerCache.Add((config, positionTrigger));
						}
					}
					foreach (ScanTriggerRule scanTrigger in config.ScanTriggers)
					{
						if (scanTrigger.Enabled)
						{
							ActiveScanTriggerCache.Add((config, scanTrigger));
						}
					}
					foreach (InteractTriggerRule interactTrigger in config.InteractTriggers)
					{
						if (interactTrigger.Enabled)
						{
							ActiveInteractTriggerCache.Add((config, interactTrigger));
						}
					}
				}
				ActiveTriggerCacheLayoutId = currentLevelLayoutId;
				ActiveTriggerCacheLayoutName = text;
				ActiveTriggerCacheDirty = false;
			}
		}

		internal static void OnExpeditionStarted()
		{
			States.Clear();
			LocalizedTextRoots.Clear();
			ZoneLookupCache.Clear();
			LastLogTimesByMessage.Clear();
			PendingConfiguredEvents.Clear();
			ClearHotPathRuntimeCaches();
			ClearTerminalSelectorCache();
			ConfigManager.LoadOrCreate(Log, force: true);
			MarkActiveTriggerCacheDirty();
			EnsureActiveTriggerCache();
			uint currentLevelLayoutId = GetCurrentLevelLayoutId();
			string value = TryGetLevelLayoutName(currentLevelLayoutId);
			int count = GetActiveTriggers().Count;
			int count2 = GetActiveScanTriggers().Count;
			int count3 = GetActiveInteractTriggers().Count;
			ScanTriggerManager.Reset();
			InteractTriggerManager.Reset();
			LogVerbose($"Expedition started. LevelLayoutID={currentLevelLayoutId}, LevelLayoutName='{value}', active coordinate triggers={count}, active scan triggers={count2}, active interact triggers={count3}, configs={ConfigManager.ConfigPathSummary}");
		}

		internal static void Tick()
		{
			try
			{
				if (Time.realtimeSinceStartup - _lastTickTime < 0.2f)
				{
					return;
				}
				_lastTickTime = Time.realtimeSinceStartup;
				ConfigManager.ProcessQueuedReload(Log);
				if (!GameStateManager.IsInExpedition)
				{
					DebugMarkerManager.Clear();
					return;
				}
				if (ShouldDumpRuntimeBindings())
				{
					ScanTriggerManager.DumpScanIndexesIfNeeded();
					InteractTriggerManager.DumpTargetIndexesIfNeeded();
				}
				WarmTerminalSelectorCacheIfNeeded();
				ProcessQueuedConfiguredEvents();
				InteractTriggerManager.ProcessPendingTerminalEvents();
				InteractTriggerManager.ProcessTerminalRepeatEvents();
				InteractTriggerManager.ProcessBigPickupRepeatEvents();
				ScanTriggerManager.ProcessScanRepeatEvents();
				List<(ConfigDocument, PositionTriggerRule)> activeTriggers = GetActiveTriggers();
				DebugMarkerManager.UpdateMarkers(activeTriggers);
				if (activeTriggers.Count == 0)
				{
					return;
				}
				foreach (var (config, trigger) in activeTriggers)
				{
					EvaluateTrigger(config, trigger);
				}
			}
			catch (Exception ex)
			{
				LogThrottled("Tick failed: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private static List<(ConfigDocument Config, PositionTriggerRule Trigger)> GetActiveTriggers()
		{
			EnsureActiveTriggerCache();
			return ActivePositionTriggerCache;
		}

		internal static List<(ConfigDocument Config, ScanTriggerRule Trigger)> GetActiveScanTriggers()
		{
			EnsureActiveTriggerCache();
			return ActiveScanTriggerCache;
		}

		internal static List<(ConfigDocument Config, InteractTriggerRule Trigger)> GetActiveInteractTriggers()
		{
			EnsureActiveTriggerCache();
			return ActiveInteractTriggerCache;
		}

		private static bool ShouldDumpRuntimeBindings()
		{
			return ConfigManager.ShouldDumpRuntimeIndexes();
		}

		private static int QueueConfiguredEventList(IEnumerable<JsonElement> events, string ownerLabel, float delaySeconds = 0.05f)
		{
			int num = 0;
			float dueTime = Time.realtimeSinceStartup + Math.Max(0f, delaySeconds);
			foreach (JsonElement @event in events)
			{
				PendingConfiguredEvents.Enqueue(new PendingConfiguredEvent
				{
					EventElement = @event.Clone(),
					OwnerLabel = ownerLabel,
					DueTime = dueTime
				});
				num++;
			}
			return num;
		}

		private static void ProcessQueuedConfiguredEvents()
		{
			if (PendingConfiguredEvents.Count == 0)
			{
				return;
			}
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			int num = 0;
			while (PendingConfiguredEvents.Count > 0 && num < 3)
			{
				PendingConfiguredEvent pendingConfiguredEvent = PendingConfiguredEvents.Peek();
				if (!(pendingConfiguredEvent.DueTime > realtimeSinceStartup))
				{
					PendingConfiguredEvents.Dequeue();
					TryExecuteConfiguredEvent(pendingConfiguredEvent.EventElement, pendingConfiguredEvent.OwnerLabel);
					num++;
					continue;
				}
				break;
			}
		}

		internal static void FireInteractTrigger(string sourceKind, string eventName, Component? source, PlayerAgent? player, string sourceName, string stateKeySuffix)
		{
			int firedDispatchCount = InteractTriggerManager.FiredDispatchCount;
			foreach (var (config, trigger) in GetActiveInteractTriggers())
			{
				FireInteractTrigger(config, trigger, sourceKind, eventName, source, player, sourceName, stateKeySuffix);
			}
			if (InteractTriggerManager.FiredDispatchCount == firedDispatchCount && (NormalizeTargetType(sourceKind) == "bigpickup" || NormalizeTargetType(sourceKind) == "terminal"))
			{
				LogVerbose($"No interact trigger matched. Event={eventName}, TargetType={sourceKind}, Source={sourceName}. Check Index/SerialNumber/ItemKey/TerminalSelector in config.");
			}
		}

		internal static void FireBigPickupCycleEvents(Component source, PlayerAgent? player, string sourceName, int completedCycles)
		{
			foreach (var (configDocument, interactTriggerRule) in GetActiveInteractTriggers())
			{
				if (!interactTriggerRule.UsePickupDropCycleEvents || interactTriggerRule.PickupDropCycleEvents.Count == 0 || (interactTriggerRule.RequireInExpedition && !GameStateManager.IsInExpedition) || !InteractTriggerMatches(interactTriggerRule, "BigPickup", "OnBigPickupDrop", source, ignoreTriggerMode: true))
				{
					continue;
				}
				int num = Math.Max(1, interactTriggerRule.PickupDropCycleCount);
				if (completedCycles % num == 0)
				{
					string key = configDocument.FilePath + "::interact-cycle::" + interactTriggerRule.ID + "::" + InteractTriggerManager.GetRuntimeIndex("BigPickup", source) + "::" + completedCycles;
					if (!InteractTriggerManager.RuleStates.TryGetValue(key, out TriggerState value))
					{
						value = new TriggerState();
						InteractTriggerManager.RuleStates[key] = value;
					}
					if (!(interactTriggerRule.Cooldown > 0f) || !(Time.realtimeSinceStartup - value.LastFireTime < interactTriggerRule.Cooldown))
					{
						value.LastFireTime = Time.realtimeSinceStartup;
						value.Fired = true;
						int value2 = ExecuteEventList(interactTriggerRule.PickupDropCycleEvents, $"BigPickup pickup/drop cycle trigger '{interactTriggerRule.ID}' cycles={completedCycles}");
						LogVerbose($"BigPickup pickup/drop cycle trigger '{interactTriggerRule.ID}' fired. Cycles={completedCycles}, Required={num}, Source={sourceName}, ExecutedEvents={value2}");
					}
				}
			}
		}

		private static void FireInteractTrigger(ConfigDocument config, InteractTriggerRule trigger, string sourceKind, string eventName, Component? source, PlayerAgent? player, string sourceName, string stateKeySuffix)
		{
			if ((trigger.RequireInExpedition && !GameStateManager.IsInExpedition) || !InteractTriggerMatches(trigger, sourceKind, eventName, source))
			{
				return;
			}
			string key = config.FilePath + "::interact::" + trigger.ID + "::" + stateKeySuffix;
			if (!InteractTriggerManager.RuleStates.TryGetValue(key, out TriggerState value))
			{
				value = new TriggerState();
				InteractTriggerManager.RuleStates[key] = value;
			}
			if (trigger.Cooldown > 0f && Time.realtimeSinceStartup - value.LastFireTime < trigger.Cooldown)
			{
				return;
			}
			value.Fired = true;
			value.LastFireTime = Time.realtimeSinceStartup;
			int num = 0;
			IEnumerable<JsonElement> enumerable = trigger.Events.Concat(trigger.WardenEvents);
			bool flag = NormalizeTargetType(sourceKind) == "terminal" && (NormalizeInteractionTriggerMode(eventName) == "onterminaluse" || NormalizeInteractionTriggerMode(eventName) == "onterminalunused");
			if (flag)
			{
				num = QueueConfiguredEventList(enumerable, "Interact trigger '" + trigger.ID + "'");
			}
			else
			{
				foreach (JsonElement item in enumerable)
				{
					if (TryExecuteConfiguredEvent(item, "Interact trigger '" + trigger.ID + "'"))
					{
						num++;
					}
				}
			}
			InteractTriggerManager.FiredDispatchCount++;
			LogVerbose($"Interact trigger '{trigger.ID}' fired. Event={eventName}, TargetType={sourceKind}, Source={sourceName}, {(flag ? "QueuedEvents" : "ExecutedEvents")}={num}");
		}

		private static bool InteractTriggerMatches(InteractTriggerRule trigger, string sourceKind, string eventName, Component? source, bool ignoreTriggerMode = false)
		{
			//IL_03fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0400: Unknown result type (might be due to invalid IL or missing references)
			//IL_0410: Unknown result type (might be due to invalid IL or missing references)
			//IL_0415: Unknown result type (might be due to invalid IL or missing references)
			//IL_0429: Unknown result type (might be due to invalid IL or missing references)
			//IL_042b: Unknown result type (might be due to invalid IL or missing references)
			//IL_042d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0432: Unknown result type (might be due to invalid IL or missing references)
			string text = NormalizeTargetType(trigger.TargetType);
			string text2 = NormalizeTargetType(sourceKind);
			if (!string.IsNullOrWhiteSpace(text) && text != "any" && text != text2)
			{
				return false;
			}
			if (!ignoreTriggerMode)
			{
				string text3 = NormalizeInteractionTriggerMode(trigger.TriggerMode);
				string text4 = NormalizeInteractionTriggerMode(eventName);
				if (!string.IsNullOrWhiteSpace(text3) && text3 != text4)
				{
					return false;
				}
			}
			if ((Object)(object)source == (Object)null)
			{
				return true;
			}
			if (trigger.Index < 0 && trigger.InstanceID < 0 && trigger.SyncID < 0 && trigger.SerialNumber < 0 && trigger.DataBlockID == 0 && trigger.ItemID == 0 && string.IsNullOrWhiteSpace(trigger.ItemKey) && string.IsNullOrWhiteSpace(trigger.PublicName) && string.IsNullOrWhiteSpace(trigger.TerminalSerial) && string.IsNullOrWhiteSpace(trigger.WorldEventObjectFilter) && string.IsNullOrWhiteSpace(trigger.InternalName) && trigger.Position == null)
			{
				return true;
			}
			if (trigger.Index >= 0)
			{
				if (text2 == "bigpickup")
				{
					if (trigger.Index <= 0)
					{
						LogThrottled($"BigPickup trigger '{trigger.ID}' uses invalid Index={trigger.Index}. ScanPosOverride/ECC BigPickup item indices start at 1.");
						return false;
					}
					if (!SpoIndexResolver.TryGetBigPickupSpoIndex(source, out int index, out string _))
					{
						LogThrottled($"BigPickup trigger '{trigger.ID}' cannot match: SPO/ECC BigPickup Item Index is unavailable. ConfigIndex={trigger.Index}, Source={((source != null) ? ((Object)source).name : null) ?? "<null>"}");
						return false;
					}
					if (index != trigger.Index)
					{
						return false;
					}
				}
				else
				{
					int index = InteractTriggerManager.GetRuntimeIndex(text2, source);
					if (index < 0)
					{
						index = TryGetIntMember(source, "Index", "m_index", "m_terminalIndex", "m_terminalSerialIndex", "PuzzleOverrideIndex");
					}
					if (index != trigger.Index)
					{
						return false;
					}
				}
			}
			if (!string.IsNullOrWhiteSpace(trigger.WorldEventObjectFilter) && !ObjectMatchesFilter(source, trigger.WorldEventObjectFilter))
			{
				return false;
			}
			if (!string.IsNullOrWhiteSpace(trigger.InternalName) && TryGetPublicObjectName(source).IndexOf(trigger.InternalName, StringComparison.OrdinalIgnoreCase) < 0)
			{
				return false;
			}
			if (trigger.DataBlockID != 0 && TryGetUIntMember(source, "DataBlockID", "ItemDataBlockID", "m_dataBlockID", "m_itemDataBlockID", "ItemID", "m_itemID") != trigger.DataBlockID)
			{
				return false;
			}
			if (trigger.InstanceID >= 0)
			{
				int num = -1;
				try
				{
					num = ((Object)source).GetInstanceID();
				}
				catch
				{
				}
				if (num != trigger.InstanceID)
				{
					return false;
				}
			}
			if (trigger.SyncID >= 0 && TryGetIntMember(source, "SyncID", "m_syncID") != trigger.SyncID)
			{
				return false;
			}
			if (trigger.SerialNumber >= 0 && TryGetIntMember(source, "m_serialNumber", "SerialNumber", "m_serial") != trigger.SerialNumber)
			{
				return false;
			}
			if (!string.IsNullOrWhiteSpace(trigger.ItemKey) && !string.Equals(TryGetStringMember(source, "m_itemKey", "ItemKey", "Key"), trigger.ItemKey, StringComparison.OrdinalIgnoreCase))
			{
				return false;
			}
			if (!string.IsNullOrWhiteSpace(trigger.TerminalSerial))
			{
				if (text2 != "terminal")
				{
					return false;
				}
				if (!TerminalMatchesSelector(source, trigger.TerminalSerial))
				{
					return false;
				}
			}
			if (!string.IsNullOrWhiteSpace(trigger.PublicName) && TryGetPublicObjectName(source).IndexOf(trigger.PublicName, StringComparison.OrdinalIgnoreCase) < 0)
			{
				return false;
			}
			if (trigger.Position != null)
			{
				Vector3 position;
				try
				{
					position = source.transform.position;
				}
				catch
				{
					return false;
				}
				Vector3 val = trigger.Position.ToVector3();
				float num2 = Math.Max(0.01f, trigger.Radius);
				Vector3 val2 = position - val;
				if (((Vector3)(ref val2)).sqrMagnitude > num2 * num2)
				{
					return false;
				}
			}
			return true;
		}

		internal static string NormalizeTargetType(string text)
		{
			return GetCachedNormalization(TargetTypeNormalizationCache, text, (string value) => string.IsNullOrWhiteSpace(value) ? "any" : (value.Trim().Replace("_", string.Empty).Replace("-", string.Empty)
				.Replace(" ", string.Empty)
				.ToLowerInvariant() switch
			{
				"terminal" => "terminal", 
				"computerterminal" => "terminal", 
				"interact" => "interact", 
				"interaction" => "interact", 
				"position" => "position", 
				"coordinate" => "coordinate", 
				"scan" => "scan", 
				"bioscan" => "bioscan", 
				"bigpickup" => "bigpickup", 
				"largepickup" => "bigpickup", 
				"carryitem" => "bigpickup", 
				"carryitempickup" => "bigpickup", 
				"any" => "any", 
				"all" => "any", 
				_ => value.Trim().ToLowerInvariant(), 
			}));
		}

		internal static string NormalizeInteractionTriggerMode(string text)
		{
			return GetCachedNormalization(InteractModeNormalizationCache, text, (string value) => string.IsNullOrWhiteSpace(value) ? string.Empty : (value.Trim().Replace("_", string.Empty).Replace("-", string.Empty)
				.Replace(" ", string.Empty)
				.ToLowerInvariant() switch
			{
				"pickup" => "onbigpickuppickup", 
				"pickedup" => "onbigpickuppickup", 
				"onpickup" => "onbigpickuppickup", 
				"onpickedup" => "onbigpickuppickup", 
				"bigpickuppickup" => "onbigpickuppickup", 
				"onbigpickuppickup" => "onbigpickuppickup", 
				"bigpickuppickedup" => "onbigpickuppickup", 
				"onbigpickup" => "onbigpickuppickup", 
				"held" => "onbigpickupheld", 
				"holding" => "onbigpickupheld", 
				"bigpickupheld" => "onbigpickupheld", 
				"bigpickupholding" => "onbigpickupheld", 
				"onbigpickupheld" => "onbigpickupheld", 
				"onbigpickupholding" => "onbigpickupheld", 
				"onbigpickuppickuprepeat" => "onbigpickupheld", 
				"onbigpickuppickupinside" => "onbigpickupheld", 
				"drop" => "onbigpickupdrop", 
				"dropped" => "onbigpickupdrop", 
				"ondrop" => "onbigpickupdrop", 
				"onbigpickupdrop" => "onbigpickupdrop", 
				"onbigpickupdropped" => "onbigpickupdrop", 
				"placed" => "onbigpickupplaced", 
				"inlevel" => "onbigpickupplaced", 
				"bigpickupplaced" => "onbigpickupplaced", 
				"bigpickupinlevel" => "onbigpickupplaced", 
				"onbigpickupplaced" => "onbigpickupplaced", 
				"onbigpickupinlevel" => "onbigpickupplaced", 
				"onbigpickupdroprepeat" =>