Decompiled source of CoordinateTriggerEvents v1.4.7

CoordinateTriggerEvents_v1.4.7.dll

Decompiled a day 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.InteropServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
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 SNetwork;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.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]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

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

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace CoordinateTriggerEvents
{
	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_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: 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.");
					}
					return;
				}
				string text = Path.Combine(customPath, "CoordinateTriggerEvents");
				Directory.CreateDirectory(text);
				WriteFeatureTemplatePair(Path.Combine(text, "PositionTriggers"), CreateChinesePositionTemplateJson(), CreateEnglishPositionTemplateJson(), log);
				WriteFeatureTemplatePair(Path.Combine(text, "ScanTriggers"), CreateChineseScanTemplateJson(), CreateEnglishScanTemplateJson(), log);
				WriteFeatureTemplatePair(Path.Combine(text, "InteractTriggers", "BigPickup"), CreateChineseBigPickupTemplateJson(), CreateEnglishBigPickupTemplateJson(), log);
				WriteFeatureTemplatePair(Path.Combine(text, "InteractTriggers", "Terminal"), CreateChineseTerminalTemplateJson(), CreateEnglishTerminalTemplateJson(), log);
				WriteFeatureTemplatePair(Path.Combine(text, "HudInteractTriggers"), CreateChineseHudInteractTemplateJson(), CreateEnglishHudInteractTemplateJson(), 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 void WriteFeatureTemplatePair(string folder, string chineseTemplate, string englishTemplate, ManualLogSource? log)
		{
			WriteTemplateIfMissing(Path.Combine(folder, "Template_CN.json"), chineseTemplate, log);
			WriteTemplateIfMissing(Path.Combine(folder, "Template_EN.json"), englishTemplate, log);
		}

		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)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Expected O, but got Unknown
			if (!ShouldRewriteTemplate(path, out string reason))
			{
				return;
			}
			Directory.CreateDirectory(Path.GetDirectoryName(path));
			File.WriteAllText(path, content, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false));
			if (reason == "missing")
			{
				Runtime.LogVerbose("Created template config: " + path);
			}
			else if (log != null)
			{
				bool flag = default(bool);
				BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(47, 1, ref flag);
				if (flag)
				{
					((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Replacing invalid or outdated template config: ");
					((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(path);
				}
				log.LogWarning(val);
			}
		}

		private static bool ShouldRewriteTemplate(string path, out string reason)
		{
			reason = "missing";
			if (!File.Exists(path))
			{
				return true;
			}
			try
			{
				string text = File.ReadAllText(path, Encoding.UTF8);
				if (!IsJsoncParseable(text))
				{
					reason = "invalid";
					return true;
				}
				if (!text.Contains("CoordinateTriggerEvents 1.4.7", StringComparison.Ordinal))
				{
					reason = "outdated";
					return true;
				}
			}
			catch
			{
				reason = "invalid";
				return true;
			}
			reason = "current";
			return false;
		}

		private static bool IsJsoncParseable(string content)
		{
			try
			{
				JsonDocumentOptions options = new JsonDocumentOptions
				{
					CommentHandling = JsonCommentHandling.Skip,
					AllowTrailingCommas = true
				};
				using (JsonDocument.Parse(content, options))
				{
					return true;
				}
			}
			catch
			{
				return false;
			}
		}

		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);
			}
			if (rootElement.TryGetProperty("HudInteractTriggers", out var value8) && value8.ValueKind == JsonValueKind.Array)
			{
				configDocument.HudInteractTriggers = ReadHudInteractTriggerArray(value8);
			}
			ValidateUniqueTriggerIDs(configDocument);
			return configDocument;
		}

		private static void ValidateUniqueTriggerIDs(ConfigDocument doc)
		{
			HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (PositionTriggerRule positionTrigger in doc.PositionTriggers)
			{
				Check("Position", positionTrigger.ID);
			}
			foreach (ScanTriggerRule scanTrigger in doc.ScanTriggers)
			{
				Check("Scan", scanTrigger.ID);
			}
			foreach (InteractTriggerRule interactTrigger in doc.InteractTriggers)
			{
				Check("Interact", interactTrigger.ID);
			}
			foreach (HudInteractTriggerRule hudInteractTrigger in doc.HudInteractTriggers)
			{
				Check("HudInteract", hudInteractTrigger.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>(doc.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_04da: Unknown result type (might be due to invalid IL or missing references)
			//IL_04e1: 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),
					UseTriggerArea = GetBool(item, "UseTriggerArea", defaultValue: false),
					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))
					};
				}
				interactTriggerRule.InteractTriggerArea = (interactTriggerRule.UseTriggerArea ? ReadInteractTriggerArea(item, interactTriggerRule.ID) : null);
				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");
				ReadPlayerTriggerEventsObject(item, interactTriggerRule);
				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<HudInteractTriggerRule> ReadHudInteractTriggerArray(JsonElement array)
		{
			//IL_02f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0300: Expected O, but got Unknown
			List<HudInteractTriggerRule> list = new List<HudInteractTriggerRule>();
			bool flag = default(bool);
			foreach (JsonElement item in array.EnumerateArray())
			{
				if (item.ValueKind != JsonValueKind.Object)
				{
					continue;
				}
				HudInteractTriggerRule hudInteractTriggerRule = new HudInteractTriggerRule
				{
					ID = GetString(item, "ID", GetString(item, "Id", GetString(item, "id", string.Empty))),
					Enabled = GetBool(item, "Enabled", defaultValue: true),
					Radius = GetFloat(item, "Radius", 2f),
					HudText = GetString(item, "HudText", GetString(item, "InteractionText", GetString(item, "Text", "Interact"))),
					ProgressText = GetString(item, "ProgressText", string.Empty),
					HoldTime = Math.Max(0f, GetFloat(item, "HoldTime", GetFloat(item, "InteractDuration", GetFloat(item, "Duration", 2f)))),
					Cooldown = Math.Max(0f, GetFloat(item, "Cooldown", 1f)),
					RequireInExpedition = GetBool(item, "RequireInExpedition", defaultValue: true),
					RequireAlivePlayer = GetBool(item, "RequireAlivePlayer", defaultValue: true),
					HostValidateDistance = GetBool(item, "HostValidateDistance", defaultValue: true),
					DebugVisible = GetBool(item, "DebugVisible", defaultValue: false),
					DebugColor = GetString(item, "DebugColor", "#00BFFF"),
					DebugAlpha = Mathf.Clamp01(GetFloat(item, "DebugAlpha", 0.35f)),
					DebugLabel = GetBool(item, "DebugLabel", defaultValue: true)
				};
				if (item.TryGetProperty("Position", out var value) && value.ValueKind == JsonValueKind.Object)
				{
					hudInteractTriggerRule.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))
					};
				}
				hudInteractTriggerRule.ProgressEvents = ReadHudInteractProgressEvents(item);
				hudInteractTriggerRule.CancelEvents = ReadFirstArrayProperty(item, "CancelEvents", "EventsOnCancel");
				hudInteractTriggerRule.Events = ReadFirstArrayProperty(item, "Events", "CompleteEvents", "EventsOnComplete");
				hudInteractTriggerRule.WardenEvents = ReadFirstArrayProperty(item, "WardenEvents", "CompleteWardenEvents");
				if (string.IsNullOrWhiteSpace(hudInteractTriggerRule.ID))
				{
					ManualLogSource? log = Runtime.Log;
					if (log != null)
					{
						log.LogError((object)"CTE config error: HUD interact trigger is missing required ID.");
					}
				}
				else if (hudInteractTriggerRule.Position == null)
				{
					ManualLogSource log2 = Runtime.Log;
					if (log2 != null)
					{
						BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(62, 1, ref flag);
						if (flag)
						{
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: HUD interact trigger '");
							((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(hudInteractTriggerRule.ID);
							((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' is missing Position.");
						}
						log2.LogError(val);
					}
				}
				else
				{
					list.Add(hudInteractTriggerRule);
				}
			}
			return list;
		}

		private static List<HudInteractProgressEventRule> ReadHudInteractProgressEvents(JsonElement element)
		{
			List<HudInteractProgressEventRule> list = new List<HudInteractProgressEventRule>();
			if (!element.TryGetProperty("ProgressEvents", out var value))
			{
				return list;
			}
			if (value.ValueKind == JsonValueKind.Object)
			{
				if (value.TryGetProperty("Progress", out var value2) || value.TryGetProperty("Events", out value2) || value.TryGetProperty("WardenEvents", out value2))
				{
					AddHudInteractProgressEvent(list, value);
				}
				else
				{
					foreach (JsonProperty item in value.EnumerateObject())
					{
						if (float.TryParse(item.Name, out var result) && item.Value.ValueKind == JsonValueKind.Array)
						{
							list.Add(new HudInteractProgressEventRule
							{
								Progress = NormalizeHudInteractProgress(result),
								Events = (from e in item.Value.EnumerateArray()
									select e.Clone()).ToList()
							});
						}
					}
				}
			}
			else if (value.ValueKind == JsonValueKind.Array)
			{
				foreach (JsonElement item2 in value.EnumerateArray())
				{
					if (item2.ValueKind == JsonValueKind.Object)
					{
						AddHudInteractProgressEvent(list, item2);
					}
				}
			}
			return list.OrderBy((HudInteractProgressEventRule e) => e.Progress).ToList();
		}

		private static void AddHudInteractProgressEvent(List<HudInteractProgressEventRule> result, JsonElement item)
		{
			result.Add(new HudInteractProgressEventRule
			{
				Progress = NormalizeHudInteractProgress(GetFloat(item, "Progress", -1f)),
				Events = ReadFirstArrayProperty(item, "Events", "WardenEvents")
			});
		}

		private static float NormalizeHudInteractProgress(float progress)
		{
			if (!(progress < 0f))
			{
				return Math.Clamp(progress, 0f, 1f);
			}
			return -1f;
		}

		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 PositionTriggerRule? ReadInteractTriggerArea(JsonElement obj, string ownerID)
		{
			if (!TryGetObjectProperty(obj, out var value, "TriggerArea", "InteractTriggerArea", "OverrideArea", "AreaFilter", "TriggerAreaFilter"))
			{
				return null;
			}
			PositionTriggerRule positionTriggerRule = new PositionTriggerRule
			{
				ID = (string.IsNullOrWhiteSpace(ownerID) ? "InteractTriggerArea" : (ownerID + "::InteractTriggerArea")),
				Enabled = GetBool(value, "Enabled", defaultValue: true),
				TriggerAreaMode = ((GetInt(value, "Count", GetInt(value, "AreaIndex", -1)) < 0) ? "OverrideBigZone" : "OverrideArea"),
				Radius = GetFloat(value, "Radius", 2f),
				LocalIndex = GetInt(value, "LocalIndex", GetInt(value, "ZoneLocalIndex", -1)),
				Count = GetInt(value, "Count", GetInt(value, "AreaIndex", -1)),
				Layer = GetString(value, "Layer", string.Empty),
				DimensionIndex = GetInt(value, "DimensionIndex", -1),
				RequireAlivePlayers = false,
				DebugVisible = false
			};
			if (value.TryGetProperty("Position", out var value2) && value2.ValueKind == JsonValueKind.Object)
			{
				positionTriggerRule.Position = new PositionData
				{
					x = GetFloat(value2, "x", GetFloat(value2, "X", 0f)),
					y = GetFloat(value2, "y", GetFloat(value2, "Y", 0f)),
					z = GetFloat(value2, "z", GetFloat(value2, "Z", 0f))
				};
			}
			else if (HasProperty(value, "x", "X", "y", "Y", "z", "Z"))
			{
				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))
				};
			}
			if (!positionTriggerRule.Enabled)
			{
				return null;
			}
			return positionTriggerRule;
		}

		private static bool TryGetObjectProperty(JsonElement obj, out JsonElement value, params string[] names)
		{
			foreach (string propertyName in names)
			{
				if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(propertyName, out value) && value.ValueKind == JsonValueKind.Object)
				{
					return true;
				}
			}
			value = default(JsonElement);
			return false;
		}

		private static void ReadPlayerTriggerEventsObject(JsonElement obj, InteractTriggerRule rule)
		{
			if (obj.TryGetProperty("PlayerTriggerEvents", out var value) && value.ValueKind == JsonValueKind.Object)
			{
				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 "p0":
						case "player1":
						case "slot1":
							rule.OnePlayerTriggerEvents = list;
							break;
						case "2":
						case "p1":
						case "player2":
						case "slot2":
							rule.TwoPlayerTriggerEvents = list;
							break;
						case "3":
						case "p2":
						case "player3":
						case "slot3":
							rule.ThreePlayerTriggerEvents = list;
							break;
						case "4":
						case "p3":
						case "player4":
						case "slot4":
							rule.FourPlayerTriggerEvents = list;
							break;
						}
					}
				}
			}
			if (rule.OnePlayerTriggerEvents.Count == 0)
			{
				rule.OnePlayerTriggerEvents = ReadFirstArrayProperty(obj, "OnePlayerTriggerEvents", "Player1TriggerEvents", "P0TriggerEvents");
			}
			if (rule.TwoPlayerTriggerEvents.Count == 0)
			{
				rule.TwoPlayerTriggerEvents = ReadFirstArrayProperty(obj, "TwoPlayerTriggerEvents", "Player2TriggerEvents", "P1TriggerEvents");
			}
			if (rule.ThreePlayerTriggerEvents.Count == 0)
			{
				rule.ThreePlayerTriggerEvents = ReadFirstArrayProperty(obj, "ThreePlayerTriggerEvents", "Player3TriggerEvents", "P2TriggerEvents");
			}
			if (rule.FourPlayerTriggerEvents.Count == 0)
			{
				rule.FourPlayerTriggerEvents = ReadFirstArrayProperty(obj, "FourPlayerTriggerEvents", "Player4TriggerEvents", "P3TriggerEvents");
			}
		}

		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 CreateChinesePositionTemplateJson()
		{
			return "{\r\n  // CoordinateTriggerEvents 1.4.7 - 位置触发器模板\r\n  // Enabled:false 时整个文件不生效;true 时按 MainLevelLayoutIDs 加载。\r\n  \"Enabled\": false,\r\n  // MainLevelLayoutIDs:指定生效的地图 LayoutPersistentID/名称;0 是模板占位。\r\n  \"MainLevelLayoutIDs\": 0,\r\n  \"PositionTriggers\": [\r\n    {\r\n      // ID:触发器唯一 ID;Type 700 开关触发器时使用 TargetID 匹配它。\r\n      \"ID\": \"position_radius_any_player_enter\",\r\n      // Enabled:false 时只禁用这个触发器。\r\n      \"Enabled\": true,\r\n      // TriggerAreaMode 可填:Radius / OverrideBigZone / OverrideArea。\r\n      // Radius=使用 Position+Radius 球形范围;OverrideBigZone=按 DimensionIndex/Layer/LocalIndex 匹配大区;OverrideArea=再用 Count 匹配小区。\r\n      \"TriggerAreaMode\": \"Radius\",\r\n      // Position:Radius 模式下的世界坐标中心。\r\n      \"Position\": { \"x\": 0, \"y\": 0, \"z\": 0 },\r\n      // Radius:Radius 模式下的触发半径。\r\n      \"Radius\": 5.0,\r\n      // TriggerMode 可填:AnyPlayerEnter / AnyPlayerInside / AnyPlayerExit / AllPlayersEnter / AllPlayersInside / AllPlayersExit。\r\n      \"TriggerMode\": \"AnyPlayerEnter\",\r\n      // Cooldown:触发冷却时间,单位秒;Inside 类持续触发模式建议 >= 1。\r\n      \"Cooldown\": 1.0,\r\n      // Events:满足 TriggerMode 后执行的事件组。\r\n      \"Events\": [\r\n        // Type 700 可按 ID 开关任意触发器;不写 Category 时按全局唯一 ID 匹配。\r\n        { \"Type\": 700, \"TargetID\": \"terminal_use\", \"Enabled\": false }\r\n      ]\r\n    }\r\n  ]\r\n}";
		}

		private static string CreateEnglishPositionTemplateJson()
		{
			return "{\r\n  // CoordinateTriggerEvents 1.4.7 - Position trigger template\r\n  // Enabled: false disables this whole file. true loads it when MainLevelLayoutIDs matches.\r\n  \"Enabled\": false,\r\n  // MainLevelLayoutIDs: target LayoutPersistentID/name. 0 is a placeholder.\r\n  \"MainLevelLayoutIDs\": 0,\r\n  \"PositionTriggers\": [\r\n    {\r\n      // ID: unique trigger ID, also used by Type 700 TargetID.\r\n      \"ID\": \"position_radius_any_player_enter\",\r\n      // Enabled: false disables only this trigger.\r\n      \"Enabled\": true,\r\n      // TriggerAreaMode values: Radius / OverrideBigZone / OverrideArea.\r\n      // Radius uses Position+Radius. OverrideBigZone uses DimensionIndex/Layer/LocalIndex. OverrideArea also uses Count.\r\n      \"TriggerAreaMode\": \"Radius\",\r\n      // Position: world-space center for Radius mode.\r\n      \"Position\": { \"x\": 0, \"y\": 0, \"z\": 0 },\r\n      // Radius: trigger radius for Radius mode.\r\n      \"Radius\": 5.0,\r\n      // TriggerMode values: AnyPlayerEnter / AnyPlayerInside / AnyPlayerExit / AllPlayersEnter / AllPlayersInside / AllPlayersExit.\r\n      \"TriggerMode\": \"AnyPlayerEnter\",\r\n      // Cooldown: trigger cooldown in seconds. Inside modes should generally be >= 1.\r\n      \"Cooldown\": 1.0,\r\n      // Events: event group fired when TriggerMode conditions are met.\r\n      \"Events\": [\r\n        // Type 700 can enable or disable any trigger by ID. Omit Category to match a globally unique ID.\r\n        { \"Type\": 700, \"TargetID\": \"terminal_use\", \"Enabled\": false }\r\n      ]\r\n    }\r\n  ]\r\n}";
		}

		private static string CreateChineseScanTemplateJson()
		{
			return "{\r\n  // CoordinateTriggerEvents 1.4.7 - 扫描触发器模板\r\n  // Enabled:false 时整个文件不生效;true 时按 MainLevelLayoutIDs 加载。\r\n  \"Enabled\": false,\r\n  // MainLevelLayoutIDs:指定生效的地图 LayoutPersistentID/名称;0 是模板占位。\r\n  \"MainLevelLayoutIDs\": 0,\r\n  \"ScanTriggers\": [\r\n    {\r\n      // ID:触发器唯一 ID;Type 700 开关触发器时使用 TargetID 匹配它。\r\n      \"ID\": \"scan_activated\",\r\n      // Enabled:false 时只禁用这个扫描触发器。\r\n      \"Enabled\": true,\r\n      // Index:扫描点索引;0 表示匹配本地图解析到的第 0 个扫描点。\r\n      \"Index\": 0,\r\n      // TriggerMode 可填:OnScanActivated / OnPlayerExitScan / OnAllPlayersEnterScan / OnAllPlayersInsideScan / OnAllPlayersExitScan / OnAllPlayersExitedScan。\r\n      \"TriggerMode\": \"OnScanActivated\",\r\n      // Cooldown:触发冷却时间,单位秒;Inside/Exited 持续触发模式建议 >= 1。\r\n      \"Cooldown\": 1.0,\r\n      // Events:满足 TriggerMode 后执行的事件组。\r\n      \"Events\": [\r\n        { \"Type\": 700, \"TargetID\": \"position_radius_any_player_enter\", \"Enabled\": false }\r\n      ]\r\n    }\r\n  ]\r\n}";
		}

		private static string CreateEnglishScanTemplateJson()
		{
			return "{\r\n  // CoordinateTriggerEvents 1.4.7 - Scan trigger template\r\n  // Enabled: false disables this whole file. true loads it when MainLevelLayoutIDs matches.\r\n  \"Enabled\": false,\r\n  // MainLevelLayoutIDs: target LayoutPersistentID/name. 0 is a placeholder.\r\n  \"MainLevelLayoutIDs\": 0,\r\n  \"ScanTriggers\": [\r\n    {\r\n      // ID: unique trigger ID, also used by Type 700 TargetID.\r\n      \"ID\": \"scan_activated\",\r\n      // Enabled: false disables only this scan trigger.\r\n      \"Enabled\": true,\r\n      // Index: scan index resolved in the current layout.\r\n      \"Index\": 0,\r\n      // TriggerMode values: OnScanActivated / OnPlayerExitScan / OnAllPlayersEnterScan / OnAllPlayersInsideScan / OnAllPlayersExitScan / OnAllPlayersExitedScan.\r\n      \"TriggerMode\": \"OnScanActivated\",\r\n      // Cooldown: trigger cooldown in seconds. Inside/Exited continuous modes should generally be >= 1.\r\n      \"Cooldown\": 1.0,\r\n      // Events: event group fired when TriggerMode conditions are met.\r\n      \"Events\": [\r\n        { \"Type\": 700, \"TargetID\": \"position_radius_any_player_enter\", \"Enabled\": false }\r\n      ]\r\n    }\r\n  ]\r\n}";
		}

		private static string CreateChineseBigPickupTemplateJson()
		{
			return "{\r\n  // CoordinateTriggerEvents 1.4.7 - 大物品触发器模板\r\n  // Enabled:false 时整个文件不生效;true 时按 MainLevelLayoutIDs 加载。\r\n  \"Enabled\": false,\r\n  // MainLevelLayoutIDs:指定生效的地图 LayoutPersistentID/名称;0 是模板占位。\r\n  \"MainLevelLayoutIDs\": 0,\r\n  \"InteractTriggers\": [\r\n    {\r\n      // ID:触发器唯一 ID;Type 700 开关触发器时使用 TargetID 匹配它。\r\n      \"ID\": \"bigpickup_pickup\",\r\n      // Enabled:false 时只禁用这个大物品触发器。\r\n      \"Enabled\": true,\r\n      // TargetType:大物品触发器固定写 BigPickup。\r\n      \"TargetType\": \"BigPickup\",\r\n      // TriggerMode 可填:OnBigPickupPickup / OnBigPickupHeld / OnBigPickupDrop / OnBigPickupPlaced。\r\n      // Pickup/Drop 是拾取/放下边沿;Held/Placed 是持有/已放下状态按 Cooldown 重复检测。\r\n      \"TriggerMode\": \"OnBigPickupPickup\",\r\n      // Index:大物品索引;-1 表示不按索引筛选。\r\n      \"Index\": 0,\r\n      // Cooldown:触发冷却时间,单位秒;Held/Placed 持续触发模式建议 >= 1。\r\n      \"Cooldown\": 1.0,\r\n\r\n      // false = 不限制区域,全局响应。true = Events 和 PlayerTriggerEvents 都只在 TriggerArea 内触发。\r\n      \"UseTriggerArea\": true,\r\n      \"TriggerArea\": {\r\n        // DimensionIndex:维度索引,通常主维度为 0。\r\n        \"DimensionIndex\": 0,\r\n        // Layer:层级索引,通常 MainLayer 为 0。\r\n        \"Layer\": 0,\r\n        // LocalIndex:大区 LocalIndex。\r\n        \"LocalIndex\": 0,\r\n        // Count 是小区索引:0=Area_a,1=Area_b,2=Area_c;Count=-1 表示该 LocalIndex 大区内所有小区。\r\n        \"Count\": -1\r\n      },\r\n\r\n      // Events:满足 TriggerMode 后执行的通用事件组。\r\n      \"Events\": [\r\n        { \"Type\": 700, \"TargetID\": \"bigpickup_held_repeat\", \"Enabled\": false }\r\n      ],\r\n      // PlayerTriggerEvents:按玩家槽位 1/2/3/4 分流执行的事件组。\r\n      \"PlayerTriggerEvents\": {\r\n        \"1\": [],\r\n        \"2\": [],\r\n        \"3\": [],\r\n        \"4\": []\r\n      }\r\n    }\r\n  ]\r\n}";
		}

		private static string CreateEnglishBigPickupTemplateJson()
		{
			return "{\r\n  // CoordinateTriggerEvents 1.4.7 - Big pickup trigger template\r\n  // Enabled: false disables this whole file. true loads it when MainLevelLayoutIDs matches.\r\n  \"Enabled\": false,\r\n  // MainLevelLayoutIDs: target LayoutPersistentID/name. 0 is a placeholder.\r\n  \"MainLevelLayoutIDs\": 0,\r\n  \"InteractTriggers\": [\r\n    {\r\n      // ID: unique trigger ID, also used by Type 700 TargetID.\r\n      \"ID\": \"bigpickup_pickup\",\r\n      // Enabled: false disables only this big-pickup trigger.\r\n      \"Enabled\": true,\r\n      // TargetType: use BigPickup for carry item triggers.\r\n      \"TargetType\": \"BigPickup\",\r\n      // TriggerMode values: OnBigPickupPickup / OnBigPickupHeld / OnBigPickupDrop / OnBigPickupPlaced.\r\n      // Pickup/Drop are edges. Held/Placed are repeated state checks using Cooldown.\r\n      \"TriggerMode\": \"OnBigPickupPickup\",\r\n      // Index: big pickup index. -1 disables index filtering.\r\n      \"Index\": 0,\r\n      // Cooldown: trigger cooldown in seconds. Held/Placed should generally be >= 1.\r\n      \"Cooldown\": 1.0,\r\n\r\n      // false = global response. true = Events and PlayerTriggerEvents only fire inside TriggerArea.\r\n      \"UseTriggerArea\": true,\r\n      \"TriggerArea\": {\r\n        // DimensionIndex: dimension index, usually 0 for the main dimension.\r\n        \"DimensionIndex\": 0,\r\n        // Layer: layer index, usually 0 for MainLayer.\r\n        \"Layer\": 0,\r\n        // LocalIndex: zone LocalIndex.\r\n        \"LocalIndex\": 0,\r\n        // Count is the area index: 0=Area_a, 1=Area_b, 2=Area_c. Count=-1 means every area in this LocalIndex zone.\r\n        \"Count\": -1\r\n      },\r\n\r\n      // Events: shared event group fired when TriggerMode conditions are met.\r\n      \"Events\": [\r\n        { \"Type\": 700, \"TargetID\": \"bigpickup_held_repeat\", \"Enabled\": false }\r\n      ],\r\n      // PlayerTriggerEvents: slot-based event groups for player slots 1/2/3/4.\r\n      \"PlayerTriggerEvents\": {\r\n        \"1\": [],\r\n        \"2\": [],\r\n        \"3\": [],\r\n        \"4\": []\r\n      }\r\n    }\r\n  ]\r\n}";
		}

		private static string CreateChineseTerminalTemplateJson()
		{
			return "{\r\n  // CoordinateTriggerEvents 1.4.7 - 终端触发器模板\r\n  // Enabled:false 时整个文件不生效;true 时按 MainLevelLayoutIDs 加载。\r\n  \"Enabled\": false,\r\n  // MainLevelLayoutIDs:指定生效的地图 LayoutPersistentID/名称;0 是模板占位。\r\n  \"MainLevelLayoutIDs\": 0,\r\n  \"InteractTriggers\": [\r\n    {\r\n      // ID:触发器唯一 ID;Type 700 开关触发器时使用 TargetID 匹配它。\r\n      \"ID\": \"terminal_use\",\r\n      // Enabled:false 时只禁用这个终端触发器。\r\n      \"Enabled\": true,\r\n      // TargetType:终端触发器固定写 Terminal。\r\n      \"TargetType\": \"Terminal\",\r\n      // TriggerMode 可填:OnTerminalUse / OnTerminalUsing / OnTerminalUnused / OnTerminalExited。\r\n      // Use/Unused 是进入/退出边沿;Using/Exited 是使用中/退出后状态按 Cooldown 重复检测。\r\n      \"TriggerMode\": \"OnTerminalUse\",\r\n      // TerminalSelector:终端筛选器,可写终端序列名,如 [TERMINAL_0_0_0_0]。\r\n      \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\r\n      // Cooldown:触发冷却时间,单位秒;Using/Exited 持续触发模式建议 >= 1。\r\n      \"Cooldown\": 1.0,\r\n      // Events:满足 TriggerMode 后执行的事件组。\r\n      \"Events\": [\r\n        { \"Type\": 700, \"TargetID\": \"terminal_using_repeat\", \"Enabled\": false }\r\n      ]\r\n    }\r\n  ]\r\n}";
		}

		private static string CreateEnglishTerminalTemplateJson()
		{
			return "{\r\n  // CoordinateTriggerEvents 1.4.7 - Terminal trigger template\r\n  // Enabled: false disables this whole file. true loads it when MainLevelLayoutIDs matches.\r\n  \"Enabled\": false,\r\n  // MainLevelLayoutIDs: target LayoutPersistentID/name. 0 is a placeholder.\r\n  \"MainLevelLayoutIDs\": 0,\r\n  \"InteractTriggers\": [\r\n    {\r\n      // ID: unique trigger ID, also used by Type 700 TargetID.\r\n      \"ID\": \"terminal_use\",\r\n      // Enabled: false disables only this terminal trigger.\r\n      \"Enabled\": true,\r\n      // TargetType: use Terminal for terminal triggers.\r\n      \"TargetType\": \"Terminal\",\r\n      // TriggerMode values: OnTerminalUse / OnTerminalUsing / OnTerminalUnused / OnTerminalExited.\r\n      // Use/Unused are edges. Using/Exited are repeated state checks using Cooldown.\r\n      \"TriggerMode\": \"OnTerminalUse\",\r\n      // TerminalSelector: terminal serial filter, for example [TERMINAL_0_0_0_0].\r\n      \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\r\n      // Cooldown: trigger cooldown in seconds. Using/Exited should generally be >= 1.\r\n      \"Cooldown\": 1.0,\r\n      // Events: event group fired when TriggerMode conditions are met.\r\n      \"Events\": [\r\n        { \"Type\": 700, \"TargetID\": \"terminal_using_repeat\", \"Enabled\": false }\r\n      ]\r\n    }\r\n  ]\r\n}";
		}

		private static string CreateChineseHudInteractTemplateJson()
		{
			return "{\r\n  // CoordinateTriggerEvents 1.4.7 - 坐标 HUD 交互触发器模板\r\n  // Enabled:false 时整个文件不生效;true 时按 MainLevelLayoutIDs 加载。\r\n  \"Enabled\": false,\r\n  // MainLevelLayoutIDs:指定生效的地图 LayoutPersistentID/名称;0 是模板占位。\r\n  \"MainLevelLayoutIDs\": 0,\r\n  \"HudInteractTriggers\": [\r\n    {\r\n      // ID:触发器唯一 ID;Type 700 开关触发器时使用 TargetID 匹配它。\r\n      \"ID\": \"hud_interact_01\",\r\n      // Enabled:false 时只禁用这个 HUD 交互点。\r\n      \"Enabled\": true,\r\n      // Position:触发点世界坐标。\r\n      \"Position\": { \"x\": 0, \"y\": 0, \"z\": 0 },\r\n      // Radius:原生交互小块的可选中体积半径;它只决定 HUD 是否容易被选中,不决定事件触发范围。\n      \"Radius\": 0.5,\n\r\n      // HudText:屏幕下方交互 HUD 文本;支持 Unity/TMP 富文本颜色标签,例如 <color=red>文本</color> 或 <color=#00BFFF>文本</color>。\r\n      \"HudText\": \"<color=#00BFFF>读取数据</color>\",\r\n\r\n      // HoldTime:按住 E 的读条总时间,单位秒。\r\n      \"HoldTime\": 3.0,\r\n      // Cooldown:读条完成后再次允许触发 Events 的冷却时间,单位秒。\r\n      \"Cooldown\": 1.0,\r\n      // RequireInExpedition:true 时只在关卡内响应。\r\n      \"RequireInExpedition\": true,\r\n      // RequireAlivePlayer:true 时死亡玩家不能触发。\r\n      \"RequireAlivePlayer\": true,\r\n      // HostValidateDistance:true 时 Host 只验证玩家有效/存活等基础条件;不会按 Radius 或距离否决已完成/已取消的原生交互。\n      \"HostValidateDistance\": true,\r\n\r\n      // DebugVisible:true 时在地图坐标生成半透明调试球体。\r\n      \"DebugVisible\": false,\r\n      // DebugColor:调试球体颜色,支持 #RRGGBB / #RRGGBBAA。\r\n      \"DebugColor\": \"#00BFFF\",\r\n      // DebugAlpha:调试球体透明度,0=完全透明,1=不透明。\r\n      \"DebugAlpha\": 0.35,\r\n      // DebugLabel:true 时在调试球体上方显示 ID 标签。\r\n      \"DebugLabel\": true,\r\n\r\n      // ProgressEvents:SPO 风格进度事件;Progress 可写 0-1 或 0-100,0.5/50=50%,1/100=100%;Progress=-1 表示模板占位不触发。\n      \"ProgressEvents\": {\r\n        \"Progress\": -1,\r\n        \"Events\": []\r\n      },\r\n\r\n      // CancelEvents:只有已经开始按住 E 并放弃读条时触发;读条正常完成不会触发。\n      \"CancelEvents\": [],\r\n\r\n      // Events:读条完成后触发的事件组。\r\n      \"Events\": [\r\n        // Type 700:按 ID 开关触发器;Category 可写 HudInteract,也可以省略并按全局唯一 ID 匹配。\r\n        { \"Type\": 700, \"Category\": \"HudInteract\", \"TargetID\": \"hud_interact_01\", \"Enabled\": false }\r\n      ]\r\n    }\r\n  ]\r\n}";
		}

		private static string CreateEnglishHudInteractTemplateJson()
		{
			return "{\r\n  // CoordinateTriggerEvents 1.4.7 - HUD interaction trigger template\r\n  // Enabled: false disables this whole file. true loads it when MainLevelLayoutIDs matches.\r\n  \"Enabled\": false,\r\n  // MainLevelLayoutIDs: target LayoutPersistentID/name. 0 is a placeholder.\r\n  \"MainLevelLayoutIDs\": 0,\r\n  \"HudInteractTriggers\": [\r\n    {\r\n      // ID: unique trigger ID, also used by Type 700 TargetID.\r\n      \"ID\": \"hud_interact_01\",\r\n      // Enabled: false disables only this HUD interaction point.\r\n      \"Enabled\": true,\r\n      // Position: world-space position of the interaction point.\r\n      \"Position\": { \"x\": 0, \"y\": 0, \"z\": 0 },\r\n      // Radius: selectable radius of the small native interaction block. It only affects HUD selection, not event trigger distance.\n      \"Radius\": 0.5,\n\r\n      // HudText: bottom interaction HUD text. Unity/TMP rich text color tags are passed through, for example <color=red>Text</color> or <color=#00BFFF>Text</color>.\r\n      \"HudText\": \"<color=#00BFFF>Read data</color>\",\r\n\r\n      // HoldTime: total hold-E progress duration, in seconds.\r\n      \"HoldTime\": 3.0,\r\n      // Cooldown: seconds before completed Events may fire again.\r\n      \"Cooldown\": 1.0,\r\n      // RequireInExpedition: true means this trigger only responds inside an expedition.\r\n      \"RequireInExpedition\": true,\r\n      // RequireAlivePlayer: true prevents dead players from triggering this point.\r\n      \"RequireAlivePlayer\": true,\r\n      // HostValidateDistance: true only lets the Host validate basic player state. It does not reject completed/cancelled native interactions by Radius or distance.\n      \"HostValidateDistance\": true,\r\n\r\n      // DebugVisible: true spawns a translucent debug sphere at Position.\r\n      \"DebugVisible\": false,\r\n      // DebugColor: debug sphere color, supports #RRGGBB / #RRGGBBAA.\r\n      \"DebugColor\": \"#00BFFF\",\r\n      // DebugAlpha: debug sphere opacity, 0=transparent, 1=opaque.\r\n      \"DebugAlpha\": 0.35,\r\n      // DebugLabel: true shows the trigger ID above the debug sphere.\r\n      \"DebugLabel\": true,\r\n\r\n      // ProgressEvents: SPO-style progress event. Progress accepts 0-1 or 0-100 values, 0.5/50=50%, 1/100=100%. Progress=-1 is a disabled placeholder.\n      \"ProgressEvents\": {\r\n        \"Progress\": -1,\r\n        \"Events\": []\r\n      },\r\n\r\n      // CancelEvents: fires only after the player has started holding E and abandons the hold. A normal completed hold will not fire CancelEvents.\n      \"CancelEvents\": [],\r\n\r\n      // Events: event group fired after the hold completes.\r\n      \"Events\": [\r\n        // Type 700 enables/disables triggers by ID. Category can be HudInteract or omitted for globally unique IDs.\r\n        { \"Type\": 700, \"Category\": \"HudInteract\", \"TargetID\": \"hud_interact_01\", \"Enabled\": false }\r\n      ]\r\n    }\r\n  ]\r\n}";
		}
	}
	internal static class PluginInfo
	{
		public const string GUID = "com.gtfo.coordinatetriggerevents";

		public const string NAME = "CoordinateTriggerEvents";

		public const string VERSION = "1.4.7";
	}
	[BepInPlugin("com.gtfo.coordinatetriggerevents", "CoordinateTriggerEvents", "1.4.7")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[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_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			Runtime.Log = ((BasePlugin)this).Log;
			ConfigManager.LoadOrCreate(((BasePlugin)this).Log, force: true);
			Runtime.SetupTerminalTriggerSync();
			Runtime.SetupHudInteractTriggerSync();
			EventAPI.OnExpeditionStarted += Runtime.OnExpeditionStarted;
			_harmony = new Harmony("com.gtfo.coordinatetriggerevents");
			SafePatchAll(_harmony, ((BasePlugin)this).Log);
			Runtime.LogVerbose($"{"CoordinateTriggerEvents"} {"1.4.7"} 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 static class Runtime
	{
		private sealed class CachedWardenEventBuild
		{
			public bool Success;

			public WardenObjectiveEventData? EventData;

			public string Failure = string.Empty;

			public bool FailureLogged;
		}

		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 List<JsonElement> EventGroup = new List<JsonElement>();

			public string OwnerLabel = string.Empty;

			public float DueTime;

			public bool HasSourcePosition;

			public Vector3 SourcePosition;
		}

		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 readonly Dictionary<string, CachedWardenEventBuild> WardenEventBuildCache = new Dictionary<string, CachedWardenEventBuild>(StringComparer.Ordinal);

		private const int WardenEventBuildCacheLimit = 4096;

		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;

		public const int MaxTerminalSyncSelectorBytes = 96;

		private const string TerminalTriggerSyncEventName = "com.gtfo.coordinatetriggerevents.terminal_trigger_event.v1";

		private const float TerminalSyncMessageLifetimeSeconds = 20f;

		private static bool TerminalTriggerSyncRegistered;

		private static bool IsReceivingTerminalTriggerSync;

		private static int NextTerminalSyncMessageId = 1;

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

		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 readonly List<(ConfigDocument Config, HudInteractTriggerRule Trigger)> ActiveHudInteractTriggerCache = new List<(ConfigDocument, HudInteractTriggerRule)>();

		private static bool ActiveTriggerCacheDirty = true;

		private static uint ActiveTriggerCacheLayoutId = uint.MaxValue;

		private static string ActiveTriggerCacheLayoutName = string.Empty;

		public const int MaxHudInteractTriggerIdBytes = 96;

		private const string HudInteractRequestEventName = "com.gtfo.coordinatetriggerevents.hud_interact_request.v1";

		private const string HudInteractConfirmEventName = "com.gtfo.coordinatetriggerevents.hud_interact_confirm.v1";

		private static bool HudInteractSyncRegistered;

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

		private const int MaxQueuedConfiguredEventsPerTick = 3;

		private static readonly HashSet<string> KnownLegacyOrEosEventNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
		{
			"CloseSecurityDoor_Custom", "SetTimerTitle", "AlertEnemiesInZone", "AlertEnemiesInArea", "Terminal_ShowTerminalInfoInZone", "KillEnemiesInArea", "KillEnemiesInZone", "KillEnemiesInDimension", "SpawnHibernate", "PlayGCEndSequence",
			"FF_ToggleFFCheck", "FF_AddPlayersInRangeToCheck", "FF_AddPlayersOutOfRangeToCheck", "FF_ToggleCheckOnGroup", "FF_Reset", "FF_ResetGroup", "FF_SetExpeditionFailedText", "FF_ResetExpeditionFailedText", "SetNavMarker", "ToggleDummyVisual",
			"ToggleLSFBState", "SaveCheckpoint", "SetSuccessPageCustomization", "ToggleCamaraShake", "Info_ZoneHibernate", "Info_LevelHibernate", "Output_LevelHibernateSpawnEvent", "PlayMusic", "StopMusic", "ToggleEventScanState",
			"ToggleSeamlessReload", "ResourceStation_SetEnabled", "ToggleSensorGroupState"
		};

		private const int TriggerControlEventType = 700;

		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;
			WardenEventBuildCache.Clear();
		}

		internal static void SetupTerminalTriggerSync()
		{
			if (TerminalTriggerSyncRegistered)
			{
				return;
			}
			try
			{
				NetworkAPI.RegisterEvent<CteTerminalTriggerPacket>("com.gtfo.coordinatetriggerevents.terminal_trigger_event.v1", (Action<ulong, CteTerminalTriggerPacket>)OnReceiveTerminalTriggerSync);
				TerminalTriggerSyncRegistered = true;
				LogVerbose("Registered CTE terminal trigger sync event 'com.gtfo.coordinatetriggerevents.terminal_trigger_event.v1'.");
			}
			catch (Exception ex)
			{
				LogThrottled("Failed to register CTE terminal trigger sync event 'com.gtfo.coordinatetriggerevents.terminal_trigger_event.v1': " + DescribeException(ex));
			}
		}

		internal static void SetupHudInteractTriggerSync()
		{
			if (HudInteractSyncRegistered)
			{
				return;
			}
			try
			{
				NetworkAPI.RegisterEvent<HudInteractTriggerRequestPacket>("com.gtfo.coordinatetriggerevents.hud_interact_request.v1", (Action<ulong, HudInteractTriggerRequestPacket>)HudInteractTriggerManager.OnReceiveRequest);
				NetworkAPI.RegisterEvent<HudInteractTriggerConfirmPacket>("com.gtfo.coordinatetriggerevents.hud_interact_confirm.v1", (Action<ulong, HudInteractTriggerConfirmPacket>)HudInteractTriggerManager.OnReceiveConfirm);
				HudInteractSyncRegistered = true;
				LogVerbose("Registered CTE HUD interact sync events 'com.gtfo.coordinatetriggerevents.hud_interact_request.v1' and 'com.gtfo.coordinatetriggerevents.hud_interact_confirm.v1'.");
			}
			catch (Exception ex)
			{
				LogThrottled("Failed to register CTE HUD interact sync events: " + DescribeException(ex));
			}
		}

		internal static void BroadcastHudInteractRequest(HudInteractTriggerRequestPacket packet)
		{
			if (!HudInteractSyncRegistered)
			{
				return;
			}
			try
			{
				NetworkAPI.InvokeEvent<HudInteractTriggerRequestPacket>("com.gtfo.coordinatetriggerevents.hud_interact_request.v1", packet, (SNet_ChannelType)2);
			}
			catch (Exception ex)
			{
				LogThrottled("Failed to broadcast CTE HUD interact request: " + DescribeException(ex));
			}
		}

		internal static void BroadcastHudInteractConfirm(HudInteractTriggerConfirmPacket packet)
		{
			if (!HudInteractSyncRegistered)
			{
				return;
			}
			try
			{
				NetworkAPI.InvokeEvent<HudInteractTriggerConfirmPacket>("com.gtfo.coordinatetriggerevents.hud_interact_confirm.v1", packet, (SNet_ChannelType)2);
			}
			catch (Exception ex)
			{
				LogThrottled("Failed to broadcast CTE HUD interact confirm: " + DescribeException(ex));
			}
		}

		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();
				ActiveHudInteractTriggerCache.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));
						}
					}
					foreach (HudInteractTriggerRule hudInteractTrigger in config.HudInteractTriggers)
					{
						if (hudInteractTrigger.Enabled)
						{
							ActiveHudInteractTriggerCache.Add((config, hudInteractTrigger));
						}
					}
				}
				ActiveTriggerCacheLayoutId = currentLevelLayoutId;
				ActiveTriggerCacheLayoutName = text;
				ActiveTriggerCacheDirty = false;
				PrimeWardenEventCacheForActiveTriggers();
			}
		}

		private static void PrimeWardenEventCacheForActiveTriggers()
		{
			foreach (var item5 in ActivePositionTriggerCache)
			{
				PositionTriggerRule item = item5.Trigger;
				PrimeEventList(item.Events);
				PrimeEventList(item.WardenEvents);
				PrimeEventList(item.OnePlayerEvents);
				PrimeEventList(item.TwoPlayerEvents);
				PrimeEventList(item.ThreePlayerEvents);
				PrimeEventList(item.FourPlayerEvents);
				PrimeEventList(item.TriggerCycleEvents);
			}
			foreach (var item6 in ActiveScanTriggerCache)
			{
				ScanTriggerRule item2 = item6.Trigger;
				PrimeEventList(item2.Events);
				PrimeEventList(item2.WardenEvents);
				PrimeEventList(item2.OnePlayerEvents);
				PrimeEventList(item2.TwoPlayerEvents);
				PrimeEventList(item2.ThreePlayerEvents);
				PrimeEventList(item2.FourPlayerEvents);
				PrimeEventList(item2.TriggerCycleEvents);
			}
			foreach (var item7 in ActiveInteractTriggerCache)
			{
				InteractTriggerRule item3 = item7.Trigger;
				PrimeEventList(item3.Events);
				PrimeEventList(item3.WardenEvents);
				PrimeEventList(item3.PickupDropCycleEvents);
			}
			foreach (var item8 in ActiveHudInteractTriggerCache)
			{
				HudInteractTriggerRule item4 = item8.Trigger;
				PrimeEventList(item4.Events);
				PrimeEventList(item4.WardenEvents);
				PrimeEventList(item4.CancelEvents);
				foreach (HudInteractProgressEventRule progressEvent in item4.ProgressEvents)
				{
					PrimeEventList(progressEvent.Events);
				}
			}
		}

		private static void PrimeEventList(IEnumerable<JsonElement> events)
		{
			foreach (JsonElement @event in events)
			{
				if (@event.ValueKind == JsonValueKind.Object && !IsTriggerControlEvent(@event))
				{
					TryGetCachedWardenEvent(@event, out WardenObjectiveEventData _, "CTE event precompile");
				}
			}
		}

		internal static void OnExpeditionStarted()
		{
			States.Clear();
			LocalizedTextRoots.Clear();
			ZoneLookupCache.Clear();
			LastLogTimesByMessage.Clear();
			PendingConfiguredEvents.Clear();
			WardenEventBuildCache.Clear();
			ReceivedTerminalSyncMessages.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();
			HudInteractTriggerManager.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();
				HudInteractTriggerManager.Update();
				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;
		}

		internal static List<(ConfigDocument Config, HudInteractTriggerRule Trigger)> GetActiveHudInteractTriggers()
		{
			EnsureActiveTriggerCache();
			return ActiveHudInteractTriggerCache;
		}

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

		private static string NormalizeDisplayEventType(string typeText)
		{
			string text = (typeText ?? string.Empty).Trim().Replace("_", string.Empty).Replace("-", string.Empty)
				.Replace(" ", string.Empty)
				.ToLowerInvariant();
			if (text.StartsWith("wee", StringComparison.Ordinal) && text.Length > 3)
			{
				text = text.Substring(3);
			}
			if (text.EndsWith("event", StringComparison.Ordinal) && text.Length > 5)
			{
				text = text.Substring(0, text.Length - 5);
			}
			return text;
		}

		private static bool CanExecuteConfiguredEvents(string ownerLabel)
		{
			return true;
		}

		private static bool TryGetIsMaster(string ownerLabel, out bool isMaster)
		{
			try
			{
				isMaster = SNet.IsMaster;
				return true;
			}
			catch (Exception ex)
			{
				isMaster = false;
				LogThrottled(ownerLabel + " skipped configured Events because SNet.IsMaster could not be read: " + DescribeException(ex));
				return false;
			}
		}

		private static bool ExecuteConfiguredEventOnThisPeer(JsonElement rawEvent, WardenObjectiveEventData eventData, string ownerLabel, out bool allowClientHudExecution)
		{
			allowClientHudExecution = true;
			return true;
		}

		private st