Decompiled source of CoordinateTriggerEvents v1.4.7
CoordinateTriggerEvents_v1.4.7.dll
Decompiled a day 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_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