Decompiled source of CoordinateTriggerEvents v1.6.8
CoordinateTriggerEvents_v1.6.8.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
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_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: 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, "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.6.8", 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); switch (text) { default: return text == "allplayersoutside"; case "anyplayerinside": case "allplayersinside": case "anyplayeroutside": return true; } } private static bool IsContinuousScanTriggerMode(string triggerMode) { return Runtime.NormalizeTriggerMode(triggerMode) == "onallplayersinsidescan"; } private static bool IsContinuousInteractTriggerMode(string targetType, string triggerMode) { string text = Runtime.NormalizeTargetType(targetType); string text2 = Runtime.NormalizeInteractionTriggerMode(triggerMode); if (text == "bigpickup") { if (!(text2 == "onbigpickupheld")) { return text2 == "onbigpickupplaced"; } return true; } return false; } private static List<InteractTriggerRule> ReadInteractTriggerArray(JsonElement array) { //IL_0461: Unknown result type (might be due to invalid IL or missing references) //IL_0468: 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)), 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 "{\n // CoordinateTriggerEvents 1.6.8 - 位置触发器默认模板。\n // Enabled:控制整个文件是否生效;false 表示本文件只是示例模板,不会加载其中的触发器。\n \"Enabled\": false,\n // MainLevelLayoutIDs:指定生效的关卡 LayoutPersistentID/名称;0 是占位值,实际使用时请改成目标关卡。\n \"MainLevelLayoutIDs\": 0,\n // Debug:位置触发器的可视化调试设置。\n \"Debug\": {\n // Enabled:true 时启用调试显示。\n \"Enabled\": true,\n // ShowScanMarkers:true 时显示触发范围标记。\n \"ShowScanMarkers\": true,\n // ShowNames:true 时在调试标记上显示触发器 ID。\n \"ShowNames\": true\n },\n // PositionTriggers:坐标/区域触发器列表。\n \"PositionTriggers\": [\n {\n // ID:触发器唯一 ID,可被 Type 700 的 TargetID 用来开启/关闭。\n \"ID\": \"position_radius_any_player_enter\",\n // Enabled:控制此单个触发器是否启用。\n \"Enabled\": true,\n // TriggerAreaMode:触发区域模式,可填 Radius / OverrideBigZone / OverrideArea。\n \"TriggerAreaMode\": \"Radius\",\n // Position:Radius 模式下的世界坐标中心点。\n \"Position\": { \"x\": 0, \"y\": 0, \"z\": 0 },\n // Radius:Radius 模式下的触发半径,单位为游戏世界距离。\n \"Radius\": 5.0,\n // DimensionIndex:OverrideBigZone/OverrideArea 使用的维度索引;-1 表示不按此字段筛选。\n \"DimensionIndex\": -1,\n // Layer:OverrideBigZone/OverrideArea 使用的层级;空字符串表示不按此字段筛选。\n \"Layer\": \"\",\n // LocalIndex:OverrideBigZone/OverrideArea 使用的大区 LocalIndex;-1 表示不按此字段筛选。\n \"LocalIndex\": -1,\n // Count:OverrideArea 使用的小区索引;0=Area_a,1=Area_b,-1 表示该大区内所有小区。\n \"Count\": -1,\n // TriggerMode:触发模式,可填 AnyPlayerEnter / AnyPlayerInside / AnyPlayerExit / AnyPlayerOutside / AllPlayersEnter / AllPlayersInside / AllPlayersExit / AllPlayersOutside。\n // AnyPlayerOutside:触发器曾经被玩家进入后,只要任意有效玩家在触发范围外就按 Cooldown 持续触发。\n // AllPlayersOutside:触发器曾经被玩家进入后,所有有效玩家都在触发范围外才按 Cooldown 持续触发。\n \"TriggerMode\": \"AnyPlayerEnter\",\n // Cooldown:触发冷却时间,单位秒;Inside 持续触发类模式建议不低于 1。\n \"Cooldown\": 1.0,\n // UsePlayerCountEvents:true 时根据区域内玩家人数触发 PlayerCountEvents。\n \"UsePlayerCountEvents\": false,\n // PlayerCountEvents:按区域内玩家人数 1/2/3/4 分流执行的事件组。\n \"PlayerCountEvents\": {\n // 1:区域内有 1 名玩家时执行的事件组。\n \"1\": [],\n // 2:区域内有 2 名玩家时执行的事件组。\n \"2\": [],\n // 3:区域内有 3 名玩家时执行的事件组。\n \"3\": [],\n // 4:区域内有 4 名玩家时执行的事件组。\n \"4\": []\n },\n // UseTriggerCycleEvents:true 时按成功触发次数累计触发 TriggerCycleEvents。\n \"UseTriggerCycleEvents\": false,\n // TriggerCycleCount:累计多少次成功触发后额外执行 TriggerCycleEvents。\n \"TriggerCycleCount\": 3,\n // TriggerCycleEvents:达到 TriggerCycleCount 后额外执行的事件组。\n \"TriggerCycleEvents\": [],\n // Events:满足 TriggerMode 条件后执行的主事件组。\n \"Events\": [\n // Type:事件类型;700 是 CTE 自定义的触发器开关事件。\n { \"Type\": 700, \"TargetID\": \"position_radius_any_player_enter\", \"Enabled\": false }\n ]\n }\n ]\n}"; } private static string CreateEnglishPositionTemplateJson() { return "{\n // CoordinateTriggerEvents 1.6.8 - PositionTriggers default template.\n // Enabled: controls whether this whole file is loaded; false makes this file a disabled example.\n \"Enabled\": false,\n // MainLevelLayoutIDs: target LayoutPersistentID/name; 0 is a placeholder for the actual expedition layout.\n \"MainLevelLayoutIDs\": 0,\n // Debug: visual debug settings for position triggers.\n \"Debug\": {\n // Enabled: true enables debug display.\n \"Enabled\": true,\n // ShowScanMarkers: true shows trigger range markers.\n \"ShowScanMarkers\": true,\n // ShowNames: true shows trigger IDs above debug markers.\n \"ShowNames\": true\n },\n // PositionTriggers: list of coordinate/area triggers.\n \"PositionTriggers\": [\n {\n // ID: unique trigger ID, also used by Type 700 TargetID.\n \"ID\": \"position_radius_any_player_enter\",\n // Enabled: controls this single trigger.\n \"Enabled\": true,\n // TriggerAreaMode: area mode, allowed values are Radius / OverrideBigZone / OverrideArea.\n \"TriggerAreaMode\": \"Radius\",\n // Position: world-space center used by Radius mode.\n \"Position\": { \"x\": 0, \"y\": 0, \"z\": 0 },\n // Radius: trigger radius in world units for Radius mode.\n \"Radius\": 5.0,\n // DimensionIndex: dimension filter for OverrideBigZone/OverrideArea; -1 disables this filter.\n \"DimensionIndex\": -1,\n // Layer: layer filter for OverrideBigZone/OverrideArea; empty string disables this filter.\n \"Layer\": \"\",\n // LocalIndex: zone LocalIndex filter for OverrideBigZone/OverrideArea; -1 disables this filter.\n \"LocalIndex\": -1,\n // Count: area index for OverrideArea; 0=Area_a, 1=Area_b, -1 means every area in the zone.\n \"Count\": -1,\n // TriggerMode: allowed values are AnyPlayerEnter / AnyPlayerInside / AnyPlayerExit / AnyPlayerOutside / AllPlayersEnter / AllPlayersInside / AllPlayersExit / AllPlayersOutside.\n // AnyPlayerOutside: after this trigger has been entered once, continues while outside when any eligible player is outside the trigger area.\n // AllPlayersOutside: after this trigger has been entered once, continues while outside when all eligible players are outside the trigger area.\n \"TriggerMode\": \"AnyPlayerEnter\",\n // Cooldown: trigger cooldown in seconds; Inside repeat modes should generally be at least 1.\n \"Cooldown\": 1.0,\n // UsePlayerCountEvents: true enables PlayerCountEvents by player count inside the area.\n \"UsePlayerCountEvents\": false,\n // PlayerCountEvents: event groups selected by player count 1/2/3/4 inside the area.\n \"PlayerCountEvents\": {\n // 1: events for one player inside.\n \"1\": [],\n // 2: events for two players inside.\n \"2\": [],\n // 3: events for three players inside.\n \"3\": [],\n // 4: events for four players inside.\n \"4\": []\n },\n // UseTriggerCycleEvents: true fires TriggerCycleEvents after repeated successful triggers.\n \"UseTriggerCycleEvents\": false,\n // TriggerCycleCount: number of successful triggers required before TriggerCycleEvents fires.\n \"TriggerCycleCount\": 3,\n // TriggerCycleEvents: extra events fired when TriggerCycleCount is reached.\n \"TriggerCycleEvents\": [],\n // Events: main event group fired when TriggerMode conditions are met.\n \"Events\": [\n // Type: event type; 700 is CTE's custom trigger enable/disable event.\n { \"Type\": 700, \"TargetID\": \"position_radius_any_player_enter\", \"Enabled\": false }\n ]\n }\n ]\n}"; } private static string CreateChineseScanTemplateJson() { return "{\n // CoordinateTriggerEvents 1.6.8 - 扫描点触发器默认模板。\n // Enabled:控制整个文件是否生效;false 表示本文件只是示例模板。\n \"Enabled\": false,\n // MainLevelLayoutIDs:指定生效的关卡 LayoutPersistentID/名称;0 是占位值。\n \"MainLevelLayoutIDs\": 0,\n // ScanTriggers:附加到安全扫描点上的触发器列表。\n \"ScanTriggers\": [\n {\n // ID:触发器唯一 ID,可被 Type 700 的 TargetID 用来开启/关闭。\n \"ID\": \"scan_activated\",\n // Enabled:控制此单个扫描点触发器是否启用。\n \"Enabled\": true,\n // Index:扫描点索引,对应 SPO/本插件解析到的扫描点编号。\n \"Index\": 0,\n // TriggerMode:可填 OnScanActivated / OnPlayerExitScan / OnAllPlayersEnterScan / OnAllPlayersInsideScan / OnAllPlayersExitScan。\n \"TriggerMode\": \"OnScanActivated\",\n // Cooldown:触发冷却时间,单位秒;Inside 持续触发类模式建议不低于 1。\n \"Cooldown\": 1.0,\n // UsePlayerCountEvents:true 时根据扫描点内玩家人数触发 PlayerCountEvents。\n \"UsePlayerCountEvents\": false,\n // PlayerCountEvents:按扫描点内玩家人数 1/2/3/4 分流执行的事件组。\n \"PlayerCountEvents\": {\n // 1:扫描点内有 1 名玩家时执行的事件组。\n \"1\": [],\n // 2:扫描点内有 2 名玩家时执行的事件组。\n \"2\": [],\n // 3:扫描点内有 3 名玩家时执行的事件组。\n \"3\": [],\n // 4:扫描点内有 4 名玩家时执行的事件组。\n \"4\": []\n },\n // UseTriggerCycleEvents:true 时按成功触发次数累计触发 TriggerCycleEvents。\n \"UseTriggerCycleEvents\": false,\n // TriggerCycleCount:累计多少次成功触发后额外执行 TriggerCycleEvents。\n \"TriggerCycleCount\": 3,\n // TriggerCycleEvents:达到 TriggerCycleCount 后额外执行的事件组。\n \"TriggerCycleEvents\": [],\n // Events:满足 TriggerMode 条件后执行的主事件组。\n \"Events\": [\n // Type:事件类型;700 是 CTE 自定义的触发器开关事件。\n { \"Type\": 700, \"TargetID\": \"position_radius_any_player_enter\", \"Enabled\": false }\n ]\n }\n ]\n}"; } private static string CreateEnglishScanTemplateJson() { return "{\n // CoordinateTriggerEvents 1.6.8 - ScanTriggers default template.\n // Enabled: controls whether this whole file is loaded; false makes this file a disabled example.\n \"Enabled\": false,\n // MainLevelLayoutIDs: target LayoutPersistentID/name; 0 is a placeholder.\n \"MainLevelLayoutIDs\": 0,\n // ScanTriggers: trigger list attached to security scans.\n \"ScanTriggers\": [\n {\n // ID: unique trigger ID, also used by Type 700 TargetID.\n \"ID\": \"scan_activated\",\n // Enabled: controls this single scan trigger.\n \"Enabled\": true,\n // Index: scan index resolved by SPO/CTE runtime scan lookup.\n \"Index\": 0,\n // TriggerMode: allowed values are OnScanActivated / OnPlayerExitScan / OnAllPlayersEnterScan / OnAllPlayersInsideScan / OnAllPlayersExitScan.\n \"TriggerMode\": \"OnScanActivated\",\n // Cooldown: trigger cooldown in seconds; Inside repeat mode should generally be at least 1.\n \"Cooldown\": 1.0,\n // UsePlayerCountEvents: true enables PlayerCountEvents by player count inside the scan.\n \"UsePlayerCountEvents\": false,\n // PlayerCountEvents: event groups selected by player count 1/2/3/4 inside the scan.\n \"PlayerCountEvents\": {\n // 1: events for one player inside the scan.\n \"1\": [],\n // 2: events for two players inside the scan.\n \"2\": [],\n // 3: events for three players inside the scan.\n \"3\": [],\n // 4: events for four players inside the scan.\n \"4\": []\n },\n // UseTriggerCycleEvents: true fires TriggerCycleEvents after repeated successful triggers.\n \"UseTriggerCycleEvents\": false,\n // TriggerCycleCount: number of successful triggers required before TriggerCycleEvents fires.\n \"TriggerCycleCount\": 3,\n // TriggerCycleEvents: extra events fired when TriggerCycleCount is reached.\n \"TriggerCycleEvents\": [],\n // Events: main event group fired when TriggerMode conditions are met.\n \"Events\": [\n // Type: event type; 700 is CTE's custom trigger enable/disable event.\n { \"Type\": 700, \"TargetID\": \"position_radius_any_player_enter\", \"Enabled\": false }\n ]\n }\n ]\n}"; } private static string CreateChineseBigPickupTemplateJson() { return "{\n // CoordinateTriggerEvents 1.6.8 - 大物品触发器默认模板。\n // Enabled:控制整个文件是否生效;false 表示本文件只是示例模板。\n \"Enabled\": false,\n // MainLevelLayoutIDs:指定生效的关卡 LayoutPersistentID/名称;0 是占位值。\n \"MainLevelLayoutIDs\": 0,\n // InteractTriggers:交互类触发器列表;此文件用于 BigPickup。\n \"InteractTriggers\": [\n {\n // ID:触发器唯一 ID,可被 Type 700 的 TargetID 用来开启/关闭。\n \"ID\": \"bigpickup_pickup\",\n // Enabled:控制此单个大物品触发器是否启用。\n \"Enabled\": true,\n // TargetType:目标类型;大物品触发器固定使用 BigPickup。\n \"TargetType\": \"BigPickup\",\n // TriggerMode:可填 OnBigPickupPickup / OnBigPickupHeld / OnBigPickupDrop / OnBigPickupPlaced。\n \"TriggerMode\": \"OnBigPickupPickup\",\n // Index:大物品索引;-1 表示不按索引筛选。\n \"Index\": 0,\n // Cooldown:触发冷却时间,单位秒;Held/Placed 持续类模式建议不低于 1。\n \"Cooldown\": 1.0,\n // UseTriggerArea:false 表示全局响应;true 表示 Events 和 PlayerTriggerEvents 只在 TriggerArea 内触发。\n \"UseTriggerArea\": true,\n // TriggerArea:限制大物品触发事件生效的区域。\n \"TriggerArea\": {\n // DimensionIndex:维度索引,通常主维度为 0。\n \"DimensionIndex\": 0,\n // Layer:层级索引,通常 MainLayer 为 0。\n \"Layer\": 0,\n // LocalIndex:大区 LocalIndex。\n \"LocalIndex\": 0,\n // Count:小区索引;0=Area_a,1=Area_b,-1 表示该大区内所有小区。\n \"Count\": -1\n },\n // PlayerTriggerEvents:按拾取/放下玩家槽位 1/2/3/4 分流执行的事件组。\n \"PlayerTriggerEvents\": {\n // 1:1 号槽位玩家触发时执行的事件组。\n \"1\": [],\n // 2:2 号槽位玩家触发时执行的事件组。\n \"2\": [],\n // 3:3 号槽位玩家触发时执行的事件组。\n \"3\": [],\n // 4:4 号槽位玩家触发时执行的事件组。\n \"4\": []\n },\n // UsePickupDropCycleEvents:true 时按“拾取->放下”完整循环累计触发 PickupDropCycleEvents。\n \"UsePickupDropCycleEvents\": false,\n // PickupDropCycleCount:累计多少个拾取/放下循环后额外执行 PickupDropCycleEvents。\n \"PickupDropCycleCount\": 1,\n // PickupDropCycleEvents:达到 PickupDropCycleCount 后额外执行的事件组。\n \"PickupDropCycleEvents\": [],\n // Events:满足 TriggerMode 条件后执行的通用事件组。\n \"Events\": [\n // Type:事件类型;700 是 CTE 自定义的触发器开关事件。\n { \"Type\": 700, \"TargetID\": \"bigpickup_held_repeat\", \"Enabled\": false }\n ]\n }\n ]\n}"; } private static string CreateEnglishBigPickupTemplateJson() { return "{\n // CoordinateTriggerEvents 1.6.8 - BigPickup default template.\n // Enabled: controls whether this whole file is loaded; false makes this file a disabled example.\n \"Enabled\": false,\n // MainLevelLayoutIDs: target LayoutPersistentID/name; 0 is a placeholder.\n \"MainLevelLayoutIDs\": 0,\n // InteractTriggers: interaction trigger list; this file is for BigPickup.\n \"InteractTriggers\": [\n {\n // ID: unique trigger ID, also used by Type 700 TargetID.\n \"ID\": \"bigpickup_pickup\",\n // Enabled: controls this single big-pickup trigger.\n \"Enabled\": true,\n // TargetType: target type; BigPickup triggers use BigPickup.\n \"TargetType\": \"BigPickup\",\n // TriggerMode: allowed values are OnBigPickupPickup / OnBigPickupHeld / OnBigPickupDrop / OnBigPickupPlaced.\n \"TriggerMode\": \"OnBigPickupPickup\",\n // Index: big pickup index; -1 disables index filtering.\n \"Index\": 0,\n // Cooldown: trigger cooldown in seconds; Held/Placed repeat modes should generally be at least 1.\n \"Cooldown\": 1.0,\n // UseTriggerArea: false means global response; true means Events and PlayerTriggerEvents only fire inside TriggerArea.\n \"UseTriggerArea\": true,\n // TriggerArea: area restriction for big-pickup events.\n \"TriggerArea\": {\n // DimensionIndex: dimension index, usually 0 for the main dimension.\n \"DimensionIndex\": 0,\n // Layer: layer index, usually 0 for MainLayer.\n \"Layer\": 0,\n // LocalIndex: zone LocalIndex.\n \"LocalIndex\": 0,\n // Count: area index; 0=Area_a, 1=Area_b, -1 means every area in this zone.\n \"Count\": -1\n },\n // PlayerTriggerEvents: event groups selected by triggering player slot 1/2/3/4.\n \"PlayerTriggerEvents\": {\n // 1: events for player slot 1.\n \"1\": [],\n // 2: events for player slot 2.\n \"2\": [],\n // 3: events for player slot 3.\n \"3\": [],\n // 4: events for player slot 4.\n \"4\": []\n },\n // UsePickupDropCycleEvents: true fires PickupDropCycleEvents after full pickup->drop cycles.\n \"UsePickupDropCycleEvents\": false,\n // PickupDropCycleCount: number of pickup/drop cycles required before PickupDropCycleEvents fires.\n \"PickupDropCycleCount\": 1,\n // PickupDropCycleEvents: extra events fired when PickupDropCycleCount is reached.\n \"PickupDropCycleEvents\": [],\n // Events: shared event group fired when TriggerMode conditions are met.\n \"Events\": [\n // Type: event type; 700 is CTE's custom trigger enable/disable event.\n { \"Type\": 700, \"TargetID\": \"bigpickup_held_repeat\", \"Enabled\": false }\n ]\n }\n ]\n}"; } private static string CreateChineseHudInteractTemplateJson() { return "{\n // CoordinateTriggerEvents 1.6.8 - HUD 交互触发器默认模板。\n // Enabled:控制整个文件是否生效;false 表示本文件只是示例模板。\n \"Enabled\": false,\n // MainLevelLayoutIDs:指定生效的关卡 LayoutPersistentID/名称;0 是占位值。\n \"MainLevelLayoutIDs\": 0,\n // HudInteractTriggers:地图坐标 HUD 交互触发点列表。\n \"HudInteractTriggers\": [\n {\n // ID:触发器唯一 ID,可被 Type 700 的 TargetID 用来开启/关闭。\n \"ID\": \"hud_interact_01\",\n // Enabled:控制此单个 HUD 交互触发点是否启用。\n \"Enabled\": true,\n // Position:HUD 交互触发点的世界坐标。\n \"Position\": { \"x\": 0, \"y\": 0, \"z\": 0 },\n // Radius:原生交互点可选中的半径,影响玩家靠近后是否能看到/选中 HUD。\n \"Radius\": 0.5,\n // HudText:屏幕下方交互 HUD 文本;支持 Unity/TMP 富文本颜色标签,例如 <color=#00BFFF>Text</color>。\n \"HudText\": \"<color=#00BFFF>Read data</color>\",\n // HoldTime:按住交互键的读条总时长,单位秒。\n \"HoldTime\": 3.0,\n // Cooldown:完成读条后再次允许触发 Events 的冷却时间,单位秒。\n \"Cooldown\": 1.0,\n // DebugColor:调试可视化标记颜色,支持 #RRGGBB / #RRGGBBAA。\n \"DebugColor\": \"#00BFFF\",\n // DebugAlpha:调试可视化标记透明度,0=完全透明,1=不透明。\n \"DebugAlpha\": 0.35,\n // DebugLabel:true 时在调试标记上显示 ID 标签。\n \"DebugLabel\": true,\n // ProgressEvents:SPO 风格进度事件;Progress 可填 0-1 或 0-100,-1 表示禁用此占位示例。\n \"ProgressEvents\": {\n // Progress:触发进度点;0.5/50 表示 50%,1/100 表示完成。\n \"Progress\": -1,\n // Events:达到 Progress 后执行的事件组。\n \"Events\": []\n },\n // CancelEvents:玩家已开始按住交互键但中途放弃时执行;正常完成读条不会触发。\n \"CancelEvents\": [],\n // Events:读条完成后执行的事件组。\n \"Events\": [\n // Type:事件类型;700 是 CTE 自定义的触发器开关事件。\n { \"Type\": 700, \"Category\": \"HudInteract\", \"TargetID\": \"hud_interact_01\", \"Enabled\": false }\n ]\n }\n ]\n}"; } private static string CreateEnglishHudInteractTemplateJson() { return "{\n // CoordinateTriggerEvents 1.6.8 - HudInteractTriggers default template.\n // Enabled: controls whether this whole file is loaded; false makes this file a disabled example.\n \"Enabled\": false,\n // MainLevelLayoutIDs: target LayoutPersistentID/name; 0 is a placeholder.\n \"MainLevelLayoutIDs\": 0,\n // HudInteractTriggers: list of coordinate-based HUD interaction points.\n \"HudInteractTriggers\": [\n {\n // ID: unique trigger ID, also used by Type 700 TargetID.\n \"ID\": \"hud_interact_01\",\n // Enabled: controls this single HUD interaction point.\n \"Enabled\": true,\n // Position: world-space position of this HUD interaction point.\n \"Position\": { \"x\": 0, \"y\": 0, \"z\": 0 },\n // Radius: selectable radius of the native interaction point, affecting whether nearby players can see/select the HUD.\n \"Radius\": 0.5,\n // HudText: bottom interaction HUD text; Unity/TMP rich text color tags are supported, for example <color=#00BFFF>Text</color>.\n \"HudText\": \"<color=#00BFFF>Read data</color>\",\n // HoldTime: total hold-interact progress duration, in seconds.\n \"HoldTime\": 3.0,\n // Cooldown: seconds before completed Events may fire again.\n \"Cooldown\": 1.0,\n // DebugColor: debug marker color, supports #RRGGBB / #RRGGBBAA.\n \"DebugColor\": \"#00BFFF\",\n // DebugAlpha: debug marker opacity, 0=transparent and 1=opaque.\n \"DebugAlpha\": 0.35,\n // DebugLabel: true shows the trigger ID label on the debug marker.\n \"DebugLabel\": true,\n // ProgressEvents: SPO-style progress event; Progress accepts 0-1 or 0-100, and -1 disables this placeholder example.\n \"ProgressEvents\": {\n // Progress: progress point; 0.5/50 means 50%, 1/100 means complete.\n \"Progress\": -1,\n // Events: event group fired when Progress is reached.\n \"Events\": []\n },\n // CancelEvents: fires when the player starts holding interact and then abandons it; normal completion does not fire this list.\n \"CancelEvents\": [],\n // Events: event group fired after the hold completes.\n \"Events\": [\n // Type: event type; 700 is CTE's custom trigger enable/disable event.\n { \"Type\": 700, \"Category\": \"HudInteract\", \"TargetID\": \"hud_interact_01\", \"Enabled\": false }\n ]\n }\n ]\n}"; } } internal static class PluginInfo { public const string GUID = "com.gtfo.coordinatetriggerevents"; public const string NAME = "CoordinateTriggerEvents"; public const string VERSION = "1.6.8"; } [BepInPlugin("com.gtfo.coordinatetriggerevents", "CoordinateTriggerEvents", "1.6.8")] [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.SetupHudInteractTriggerSync(); Runtime.SetupTriggerControlWardenEvent(); EventAPI.OnExpeditionStarted += Runtime.OnExpeditionStarted; _harmony = new Harmony("com.gtfo.coordinatetriggerevents"); SafePatchAll(_harmony, ((BasePlugin)this).Log); Runtime.LogVerbose($"{"CoordinateTriggerEvents"} {"1.6.8"} 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 PositionTriggerPlayerState { public readonly int InsidePlayerCount; public readonly int EligibleInsidePlayerCount; public readonly int EligiblePlayerCount; public PositionTriggerPlayerState(int insidePlayerCount, int eligibleInsidePlayerCount, int eligiblePlayerCount) { InsidePlayerCount = insidePlayerCount; EligibleInsidePlayerCount = eligibleInsidePlayerCount; EligiblePlayerCount = eligiblePlayerCount; } } private sealed class CourseNodeDimensionMap { public List<AIG_CourseNode>?[,]? BuildNodeMap; public AIG_CourseNode[]?[,]? NodeMap; public int MinX = int.MaxValue; public int MinZ = int.MaxValue; public int MaxX = int.MinValue; public int MaxZ = int.MinValue; public int SizeX; public int SizeZ; public void UpdateBounds(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) int val = Mathf.FloorToInt(position.x / 4f); int val2 = Mathf.FloorToInt(position.z / 4f); MinX = Math.Min(MinX, val); MinZ = Math.Min(MinZ, val2); MaxX = Math.Max(MaxX, val); MaxZ = Math.Max(MaxZ, val2); } public bool CreateNodeMap() { if (MinX == int.MaxValue || MinZ == int.MaxValue || MaxX == int.MinValue || MaxZ == int.MinValue) { return false; } SizeX = Math.Max(1, MaxX - MinX + 1); SizeZ = Math.Max(1, MaxZ - MinZ + 1); BuildNodeMap = new List<AIG_CourseNode>[SizeX, SizeZ]; NodeMap = new AIG_CourseNode[SizeX, SizeZ][]; return true; } public bool AddNode(Vector3 position, AIG_CourseNode node) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (BuildNodeMap == null) { return false; } (int x, int z) mapPos = GetMapPos(position); int item = mapPos.x; int item2 = mapPos.z; List<AIG_CourseNode> list = BuildNodeMap[item, item2]; if (list == null) { list = new List<AIG_CourseNode>(); BuildNodeMap[item, item2] = list; } if (!list.Any((AIG_CourseNode existing) => existing != null && existing.NodeID == node.NodeID)) { list.Add(node); } return true; } public void FinishBuild() { if (BuildNodeMap == null || NodeMap == null) { return; } for (int i = 0; i < SizeX; i++) { for (int j = 0; j < SizeZ; j++) { NodeMap[i, j] = BuildNodeMap[i, j]?.ToArray(); } } BuildNodeMap = null; } public AIG_CourseNode[]? GetNodes(Vector3 position) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (NodeMap == null) { return null; } var (num, num2) = GetMapPos(position); if (NodeMap[num, num2] != null) { return NodeMap[num, num2]; } int num3 = Math.Max(SizeX, SizeZ); for (int i = 1; i < num3; i++) { int num4 = Math.Max(num - i, 0); int num5 = Math.Min(num + i, SizeX - 1); int num6 = Math.Max(num2 - i, 0); int num7 = Math.Min(num2 + i, SizeZ - 1); for (int j = num4; j <= num5; j++) { if (NodeMap[j, num6] != null) { return NodeMap[j, num6]; } if (NodeMap[j, num7] != null) { return NodeMap[j, num7]; } } for (int k = num6 + 1; k <= num7 - 1; k++) { if (NodeMap[num4, k] != null) { return NodeMap[num4, k]; } if (NodeMap[num5, k] != null) { return NodeMap[num5, k]; } } } return null; } private (int x, int z) GetMapPos(Vector3 position) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) int num = Mathf.FloorToInt(position.x / 4f) - MinX; return new ValueTuple<int, int>(item2: Mathf.Clamp(Mathf.FloorToInt(position.z / 4f) - MinZ, 0, SizeZ - 1), item1: Mathf.Clamp(num, 0, SizeX - 1)); } } 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<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 float CourseNodeMapIndexSize = 4f; private static readonly Dictionary<eDimensionIndex, CourseNodeDimensionMap> CourseNodePositionMaps = new Dictionary<eDimensionIndex, CourseNodeDimensionMap>(); private static int CourseNodePositionMapClusterCount = -1; private const int TriggerControlEventType = 700; private const string TriggerControlEventName = "CTETriggerControl"; private static bool TriggerControlWardenEventRegistered; 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 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); PrimeEventLi