Decompiled source of CoordinateTriggerEvents v1.1.0
CoordinateTriggerEvents_v1.1.0.dll
Decompiled 4 days 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.Versioning; using System.Security; using System.Security.Permissions; using System.Text.Json; using AIGraph; using Agents; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using ChainedPuzzles; using GTFO.API; using GameData; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using LevelGeneration; using Localization; using Microsoft.CodeAnalysis; using Player; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("CoordinateTriggerEvents")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("CoordinateTriggerEvents")] [assembly: AssemblyTitle("CoordinateTriggerEvents")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace CoordinateTriggerEvents { internal static class PluginInfo { public const string GUID = "com.gtfo.coordinatetriggerevents"; public const string NAME = "CoordinateTriggerEvents"; public const string VERSION = "1.1.0"; } [BepInPlugin("com.gtfo.coordinatetriggerevents", "CoordinateTriggerEvents", "1.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BasePlugin { private Harmony? _harmony; public override void Load() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Expected O, but got Unknown Runtime.Log = ((BasePlugin)this).Log; ConfigManager.LoadOrCreate(((BasePlugin)this).Log, force: true); EventAPI.OnExpeditionStarted += Runtime.OnExpeditionStarted; _harmony = new Harmony("com.gtfo.coordinatetriggerevents"); SafePatchAll(_harmony, ((BasePlugin)this).Log); Runtime.LogVerbose($"{"CoordinateTriggerEvents"} {"1.1.0"} loaded. Config roots={ConfigManager.ConfigPathSummary}"); } private static void SafePatchAll(Harmony harmony, ManualLogSource log) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Expected O, but got Unknown int num = 0; int num2 = 0; bool flag = default(bool); try { Type[] types = Assembly.GetExecutingAssembly().GetTypes(); foreach (Type type in types) { if (!type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Any()) { continue; } try { harmony.CreateClassProcessor(type).Patch(); num++; } catch (Exception ex) { num2++; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(36, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Optional Harmony patch skipped: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(type.FullName); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } log.LogWarning(val); } } } catch (Exception ex2) { BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(34, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("SafePatchAll failed unexpectedly: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex2); } log.LogError(val2); } Runtime.LogVerbose($"Harmony patches applied. PatchedClasses={num}, SkippedClasses={num2}"); } } internal sealed class ConfigDocument { public string FilePath = string.Empty; public bool Enabled = true; public List<JsonElement> MainLevelLayoutIDs = new List<JsonElement>(); public DebugOptions Debug = new DebugOptions(); public List<PositionTriggerRule> PositionTriggers = new List<PositionTriggerRule>(); public List<ScanTriggerRule> ScanTriggers = new List<ScanTriggerRule>(); public List<InteractTriggerRule> InteractTriggers = new List<InteractTriggerRule>(); } internal sealed class ScanTriggerRule { public string ID { get; set; } = string.Empty; public bool Enabled { get; set; } = true; public int PuzzleOverrideIndex { get; set; } = -1; public string TriggerMode { get; set; } = "OnScanActivated"; public bool UsePlayerCountEvents { get; set; } public List<JsonElement> OnePlayerEvents { get; set; } = new List<JsonElement>(); public List<JsonElement> TwoPlayerEvents { get; set; } = new List<JsonElement>(); public List<JsonElement> ThreePlayerEvents { get; set; } = new List<JsonElement>(); public List<JsonElement> FourPlayerEvents { get; set; } = new List<JsonElement>(); public float Cooldown { get; set; } public bool RequireInExpedition { get; set; } = true; public bool RequireAlivePlayers { get; set; } = true; public bool UseTriggerCycleEvents { get; set; } public int TriggerCycleCount { get; set; } = 3; public List<JsonElement> TriggerCycleEvents { get; set; } = new List<JsonElement>(); public List<JsonElement> Events { get; set; } = new List<JsonElement>(); public List<JsonElement> WardenEvents { get; set; } = new List<JsonElement>(); } internal sealed class ScanRuntimeState { public int LastPlayerCount; public bool HasObserved; public bool Fired; public float LastFireTime = -999999f; public bool IsActive; public bool ActivatedThisCycle; public bool ActivationPlayerCountEventsFired; public bool HadPlayersInside; public bool ExitTriggeredThisCycle; public bool AllPlayersInsideNow; public bool AllPlayersEnteredThisCycle; public bool AllPlayersExitedRepeatActive; public int ActivationEdgeSequence; public int ExitEdgeSequence; public int AllPlayersEnterEdgeSequence; public int AllPlayersExitEdgeSequence; } internal sealed class InteractTriggerRule { public string ID { get; set; } = string.Empty; public bool Enabled { get; set; } = true; public string TargetType { get; set; } = "Any"; public string TriggerMode { get; set; } = string.Empty; public float Cooldown { get; set; } public bool RequireInExpedition { get; set; } = true; public int Index { get; set; } = -1; public int InstanceID { get; set; } = -1; public int SyncID { get; set; } = -1; public int SerialNumber { get; set; } = -1; public string ItemKey { get; set; } = string.Empty; public string PublicName { get; set; } = string.Empty; public string TerminalSerial { get; set; } = string.Empty; public string WorldEventObjectFilter { get; set; } = string.Empty; public uint DataBlockID { get; set; } public uint ItemID { get; set; } public string InternalName { get; set; } = string.Empty; public PositionData? Position { get; set; } public float Radius { get; set; } = 2f; public bool UsePickupDropCycleEvents { get; set; } public int PickupDropCycleCount { get; set; } = 3; public List<JsonElement> PickupDropCycleEvents { get; set; } = new List<JsonElement>(); public List<JsonElement> Events { get; set; } = new List<JsonElement>(); public List<JsonElement> WardenEvents { get; set; } = new List<JsonElement>(); } internal sealed class DebugOptions { public bool Enabled; public bool ShowScanMarkers = true; public bool ShowNames = true; public string MarkerColor = "#00BFFF"; public string LabelColor = "#FFFFFF"; public float MarkerAlpha = 0.35f; public float HeightOffset = 0.05f; public float MarkerHeight = 0.025f; public float LabelHeightOffset = 1f; public float RadiusScale = 1f; public float MinimumRadius = 0.5f; public float RefreshInterval = 1f; public bool DumpRuntimeIndexes; } internal sealed class PositionTriggerRule { public string ID { get; set; } = string.Empty; public bool Enabled { get; set; } = true; public PositionData? Position { get; set; } public string TriggerAreaMode { get; set; } = "Radius"; public float Radius { get; set; } = 3f; public int LocalIndex { get; set; } = -1; public int Count { get; set; } = -1; public string Layer { get; set; } = string.Empty; public int DimensionIndex { get; set; } = -1; public string TriggerMode { get; set; } = "AnyPlayerEnter"; public bool UsePlayerCountEvents { get; set; } public bool UseTriggerCycleEvents { get; set; } public int TriggerCycleCount { get; set; } = 3; public List<JsonElement> TriggerCycleEvents { get; set; } = new List<JsonElement>(); public List<JsonElement> OnePlayerEvents { get; set; } = new List<JsonElement>(); public List<JsonElement> TwoPlayerEvents { get; set; } = new List<JsonElement>(); public List<JsonElement> ThreePlayerEvents { get; set; } = new List<JsonElement>(); public List<JsonElement> FourPlayerEvents { get; set; } = new List<JsonElement>(); public float Cooldown { get; set; } public bool RequireInExpedition { get; set; } = true; public bool RequireAlivePlayers { get; set; } = true; public bool IncludeBots { get; set; } = true; public bool DebugVisible { get; set; } = true; public string DebugColor { get; set; } = string.Empty; public List<JsonElement> Events { get; set; } = new List<JsonElement>(); public List<JsonElement> WardenEvents { get; set; } = new List<JsonElement>(); } internal sealed class PositionData { public float x { get; set; } public float y { get; set; } public float z { get; set; } public Vector3 ToVector3() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return new Vector3(x, y, z); } } internal sealed class TriggerState { public bool WasInside; public bool Fired; public float LastFireTime = -999999f; public int LastInsidePlayerCount; public readonly HashSet<int> FiredPlayerCounts = new HashSet<int>(); public int CompletedCycles; } internal static class ConfigManager { internal const string ConfigFolderName = "CoordinateTriggerEvents"; internal const string TemplateChineseFileName = "Template_CN.json"; internal const string TemplateEnglishFileName = "Template_EN.json"; internal static readonly List<ConfigDocument> Configs = new List<ConfigDocument>(); private static readonly object LockObject = new object(); private static string _pluginDir = string.Empty; private static readonly Dictionary<string, DateTime> LastWriteTimes = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, FileSystemWatcher> ConfigWatchers = new Dictionary<string, FileSystemWatcher>(StringComparer.OrdinalIgnoreCase); private static readonly TimeSpan ReloadDebounceDelay = TimeSpan.FromMilliseconds(350.0); private static bool _reloadQueued; private static DateTime _reloadNotBeforeUtc = DateTime.MinValue; private static string _reloadReason = string.Empty; private const float ContinuousTriggerMinimumCooldown = 1f; internal static string ConfigPathSummary { get { lock (LockObject) { return (Configs.Count == 0) ? "<none>" : string.Join(" | ", Configs.Select((ConfigDocument c) => c.FilePath)); } } } internal static void LoadOrCreate(ManualLogSource? log, bool force) { //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Expected O, but got Unknown lock (LockObject) { string pluginPath = Paths.PluginPath; _pluginDir = GetPluginDirectory(); Directory.CreateDirectory(pluginPath); CleanupLegacyPluginLocalTemplates(log); EnsureTemplateConfigs(log); List<string> list = GetConfigSearchRoots(log).Where(Directory.Exists).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); EnsureConfigWatchers(list, log); List<string> list2 = (from p in list.SelectMany((string root) => Directory.GetFiles(root, "*.json", SearchOption.AllDirectories)) where IsCoordinateTriggerConfigPath(p) select p).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); bool flag = RefreshFileSnapshot(list2); if (!(force || flag)) { return; } Configs.Clear(); bool flag2 = default(bool); foreach (string item in list2) { try { ConfigDocument configDocument = ParseConfig(item); if (configDocument != null) { Configs.Add(configDocument); Runtime.LogVerbose($"Loaded config: {item} | positionTriggers={configDocument.PositionTriggers.Count}, scanTriggers={configDocument.ScanTriggers.Count}, interactTriggers={configDocument.InteractTriggers.Count}"); LogTriggerEnabledStates(log, configDocument); } } catch (Exception ex) { if (log != null) { BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(28, 3, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to load config '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(item); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } log.LogError(val); } } } Runtime.ClearConfigurationResolutionCaches(); Runtime.MarkActiveTriggerCacheDirty(); } } private static void LogTriggerEnabledStates(ManualLogSource? log, ConfigDocument doc) { foreach (PositionTriggerRule positionTrigger in doc.PositionTriggers) { Runtime.LogVerbose($"Loaded position trigger '{positionTrigger.ID}' Enabled={positionTrigger.Enabled}{(positionTrigger.Enabled ? string.Empty : " (skipped until enabled)")}"); } foreach (ScanTriggerRule scanTrigger in doc.ScanTriggers) { Runtime.LogVerbose($"Loaded scan trigger '{scanTrigger.ID}' Enabled={scanTrigger.Enabled}{(scanTrigger.Enabled ? string.Empty : " (skipped until enabled)")}"); } foreach (InteractTriggerRule interactTrigger in doc.InteractTriggers) { Runtime.LogVerbose($"Loaded interact trigger '{interactTrigger.ID}' Enabled={interactTrigger.Enabled}{(interactTrigger.Enabled ? string.Empty : " (skipped until enabled)")}"); } } internal static void ProcessQueuedReload(ManualLogSource? log) { string reloadReason; lock (LockObject) { if (!_reloadQueued || DateTime.UtcNow < _reloadNotBeforeUtc) { return; } _reloadQueued = false; reloadReason = _reloadReason; _reloadReason = string.Empty; } Runtime.LogVerbose("Config file save detected; reloading CTE configs. Reason=" + reloadReason); LoadOrCreate(log, force: true); } private static void QueueReloadFromWatcher(string reason) { lock (LockObject) { _reloadQueued = true; _reloadNotBeforeUtc = DateTime.UtcNow + ReloadDebounceDelay; _reloadReason = reason; } } private static bool RefreshFileSnapshot(List<string> files) { bool result = files.Count != LastWriteTimes.Count; HashSet<string> hashSet = new HashSet<string>(files, StringComparer.OrdinalIgnoreCase); foreach (string file in files) { DateTime dateTime = SafeGetLastWriteTimeUtc(file); if (!LastWriteTimes.TryGetValue(file, out var value) || value != dateTime) { result = true; LastWriteTimes[file] = dateTime; } } foreach (string item in LastWriteTimes.Keys.ToList()) { if (!hashSet.Contains(item)) { result = true; LastWriteTimes.Remove(item); } } return result; } private static DateTime SafeGetLastWriteTimeUtc(string file) { try { return File.Exists(file) ? File.GetLastWriteTimeUtc(file) : DateTime.MinValue; } catch { return DateTime.MinValue; } } private static void EnsureConfigWatchers(List<string> customRoots, ManualLogSource? log) { //IL_01fe: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Expected O, but got Unknown HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string customRoot in customRoots) { string path = Path.Combine(customRoot, "CoordinateTriggerEvents"); if (Directory.Exists(path)) { hashSet.Add(Path.GetFullPath(path)); } } foreach (string item in ConfigWatchers.Keys.ToList()) { if (!hashSet.Contains(item)) { try { ConfigWatchers[item].EnableRaisingEvents = false; ConfigWatchers[item].Dispose(); } catch { } ConfigWatchers.Remove(item); Runtime.LogVerbose("Stopped CTE config watcher: " + item); } } bool flag = default(bool); foreach (string item2 in hashSet) { if (ConfigWatchers.ContainsKey(item2)) { continue; } try { FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(item2, "*.json") { IncludeSubdirectories = true, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime) }; fileSystemWatcher.Changed += OnConfigFileChanged; fileSystemWatcher.Created += OnConfigFileChanged; fileSystemWatcher.Deleted += OnConfigFileChanged; fileSystemWatcher.Renamed += OnConfigFileRenamed; fileSystemWatcher.Error += OnConfigWatcherError; fileSystemWatcher.EnableRaisingEvents = true; ConfigWatchers[item2] = fileSystemWatcher; Runtime.LogVerbose("Started CTE config watcher: " + item2); } catch (Exception ex) { if (log != null) { BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(45, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to start CTE config watcher for '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(item2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } log.LogWarning(val); } } } } private static void OnConfigFileChanged(object sender, FileSystemEventArgs e) { if (IsCoordinateTriggerConfigPath(e.FullPath)) { QueueReloadFromWatcher($"{e.ChangeType}: {e.FullPath}"); } } private static void OnConfigFileRenamed(object sender, RenamedEventArgs e) { if (IsCoordinateTriggerConfigPath(e.FullPath) || IsCoordinateTriggerConfigPath(e.OldFullPath)) { QueueReloadFromWatcher("Renamed: " + e.OldFullPath + " -> " + e.FullPath); } } private static void OnConfigWatcherError(object sender, ErrorEventArgs e) { QueueReloadFromWatcher("WatcherError: " + e.GetException().GetType().Name); } internal static bool ShouldDumpRuntimeIndexes() { lock (LockObject) { return Configs.Any((ConfigDocument c) => c.Enabled && c.Debug.Enabled && c.Debug.DumpRuntimeIndexes); } } private static void EnsureTemplateConfigs(ManualLogSource? log) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown try { if (!TryGetMtfoCustomPath(out string customPath) || string.IsNullOrWhiteSpace(customPath)) { if (log != null) { log.LogWarning((object)"MTFO CustomPath is not available; CTE template configs were not generated. Load a custom rundown through MTFO first."); } } else { string text = Path.Combine(customPath, "CoordinateTriggerEvents"); Directory.CreateDirectory(text); WriteTemplateIfMissing(Path.Combine(text, "Template_CN.json"), CreateChineseTemplateJson(), log); WriteTemplateIfMissing(Path.Combine(text, "Template_EN.json"), CreateEnglishTemplateJson(), log); } } catch (Exception ex) { if (log != null) { bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(41, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to ensure CTE template configs: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } log.LogWarning(val); } } } private static IEnumerable<string> GetConfigSearchRoots(ManualLogSource? log) { if (TryGetMtfoCustomPath(out string customPath) && !string.IsNullOrWhiteSpace(customPath)) { yield return customPath; } else if (log != null) { log.LogWarning((object)"MTFO CustomPath is not available; CTE will not read plugin-local Custom fallback configs to avoid duplicate configuration loading."); } } private static void CleanupLegacyPluginLocalTemplates(ManualLogSource? log) { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Expected O, but got Unknown try { string text = Path.Combine(_pluginDir, "Custom", "CoordinateTriggerEvents"); if (!Directory.Exists(text)) { return; } string[] array = new string[2] { "Template_CN.json", "Template_EN.json" }; foreach (string path in array) { string text2 = Path.Combine(text, path); if (File.Exists(text2)) { File.Delete(text2); Runtime.LogVerbose("Deleted legacy plugin-local template config: " + text2); } } if (!Directory.EnumerateFileSystemEntries(text).Any()) { Directory.Delete(text); string directoryName = Path.GetDirectoryName(text); if (Directory.Exists(directoryName) && !Directory.EnumerateFileSystemEntries(directoryName).Any()) { Directory.Delete(directoryName); } } } catch (Exception ex) { if (log != null) { bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(53, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Failed to clean legacy plugin-local CTE templates: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.GetType().Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } log.LogWarning(val); } } } private static bool TryGetMtfoCustomPath(out string customPath) { customPath = string.Empty; try { if (!((BaseChainloader<BasePlugin>)(object)IL2CPPChainloader.Instance).Plugins.TryGetValue("com.dak.MTFO", out var value)) { return false; } Assembly assembly = ((value == null) ? null : value.Instance?.GetType()?.Assembly); if (assembly == null) { return false; } Type type = assembly.GetTypes().FirstOrDefault((Type t) => t.Name == "ConfigManager"); if (type == null) { return false; } FieldInfo field = type.GetField("CustomPath", BindingFlags.Static | BindingFlags.Public); FieldInfo field2 = type.GetField("HasCustomContent", BindingFlags.Static | BindingFlags.Public); if (field2 != null) { object value2 = field2.GetValue(null); if (value2 is bool && !(bool)value2) { return false; } } if (field?.GetValue(null) is string text && !string.IsNullOrWhiteSpace(text)) { customPath = text; return true; } } catch { } return false; } private static void WriteTemplateIfMissing(string path, string content, ManualLogSource? log) { if (!File.Exists(path)) { Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllText(path, content); Runtime.LogVerbose("Created template config: " + path); } } private static bool IsCoordinateTriggerConfigPath(string path) { string text = path.Replace('\\', '/'); if (text.Contains("/Custom/CoordinateTriggerEvents/", StringComparison.OrdinalIgnoreCase)) { return text.EndsWith(".json", StringComparison.OrdinalIgnoreCase); } return false; } private static string GetPluginDirectory() { try { string location = Assembly.GetExecutingAssembly().Location; if (!string.IsNullOrWhiteSpace(location)) { string directoryName = Path.GetDirectoryName(location); if (!string.IsNullOrWhiteSpace(directoryName)) { return directoryName; } } } catch { } return Path.Combine(Paths.PluginPath, "CoordinateTriggerEvents"); } private static ConfigDocument? ParseConfig(string file) { using JsonDocument jsonDocument = JsonDocument.Parse(File.ReadAllText(file), new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip }); JsonElement rootElement = jsonDocument.RootElement; ConfigDocument configDocument = new ConfigDocument { FilePath = file }; if (rootElement.ValueKind == JsonValueKind.Array) { configDocument.MainLevelLayoutIDs = new List<JsonElement>(); configDocument.PositionTriggers = ReadTriggerArray(rootElement); ValidateUniqueTriggerIDs(configDocument); return configDocument; } if (rootElement.ValueKind != JsonValueKind.Object) { return null; } configDocument.Enabled = GetBool(rootElement, "Enabled", defaultValue: true); if (rootElement.TryGetProperty("MainLevelLayoutIDs", out var value)) { configDocument.MainLevelLayoutIDs = ReadSelectorList(value); } configDocument.Debug = ReadDebugOptions(rootElement); JsonElement value3; if (rootElement.TryGetProperty("PositionTriggers", out var value2) && value2.ValueKind == JsonValueKind.Array) { configDocument.PositionTriggers = ReadTriggerArray(value2); } else if (rootElement.TryGetProperty("Triggers", out value3) && value3.ValueKind == JsonValueKind.Array) { configDocument.PositionTriggers = ReadTriggerArray(value3); } if (rootElement.TryGetProperty("ScanTriggers", out var value4) && value4.ValueKind == JsonValueKind.Array) { configDocument.ScanTriggers = ReadScanTriggerArray(value4); } JsonElement value6; JsonElement value7; if (rootElement.TryGetProperty("InteractTriggers", out var value5) && value5.ValueKind == JsonValueKind.Array) { configDocument.InteractTriggers = ReadInteractTriggerArray(value5); } else if (rootElement.TryGetProperty("InteractionTriggers", out value6) && value6.ValueKind == JsonValueKind.Array) { configDocument.InteractTriggers = ReadInteractTriggerArray(value6); } else if (rootElement.TryGetProperty("ObjectTriggers", out value7) && value7.ValueKind == JsonValueKind.Array) { configDocument.InteractTriggers = ReadInteractTriggerArray(value7); } ValidateUniqueTriggerIDs(configDocument); return configDocument; } private static void ValidateUniqueTriggerIDs(ConfigDocument doc) { ConfigDocument doc2 = doc; HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (PositionTriggerRule positionTrigger in doc2.PositionTriggers) { Check("Position", positionTrigger.ID); } foreach (ScanTriggerRule scanTrigger in doc2.ScanTriggers) { Check("Scan", scanTrigger.ID); } foreach (InteractTriggerRule interactTrigger in doc2.InteractTriggers) { Check("Interact", interactTrigger.ID); } void Check(string category, string id) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown if (!string.IsNullOrWhiteSpace(id) && !seen.Add(id)) { ManualLogSource log = Runtime.Log; if (log != null) { bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(118, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: duplicate trigger ID '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(id); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("'. Trigger IDs must be globally unique within loaded configs. Category="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(category); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(", File="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(doc2.FilePath); } log.LogError(val); } } } } private static bool TryApplyCooldownPolicy(JsonElement element, string category, string triggerId, string triggerMode, bool isContinuous, out float cooldown) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Expected O, but got Unknown cooldown = GetFloat(element, "Cooldown", 0f); if (!isContinuous) { if (cooldown < 0f) { cooldown = 0f; } return true; } bool flag = default(bool); if (!HasProperty(element, "Cooldown")) { ManualLogSource log = Runtime.Log; if (log != null) { BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(153, 4, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: continuous "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(category); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" trigger '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(triggerId); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' uses TriggerMode='"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(triggerMode); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("' but is missing required Cooldown. Continuous triggers require Cooldown >= "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<float>(1f, "0.0"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(". Trigger skipped."); } log.LogError(val); } return false; } if (cooldown < 1f) { ManualLogSource log = Runtime.Log; if (log != null) { BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(136, 5, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("CTE config warning: continuous "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(category); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" trigger '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(triggerId); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' uses TriggerMode='"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(triggerMode); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' with Cooldown="); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<float>(cooldown, "0.###"); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(". Clamped to "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<float>(1f, "0.0"); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" to match AWO StartEventLoop safety semantics."); } log.LogWarning(val2); } cooldown = 1f; } return true; } private static bool HasProperty(JsonElement obj, params string[] names) { if (obj.ValueKind != JsonValueKind.Object) { return false; } foreach (string propertyName in names) { if (obj.TryGetProperty(propertyName, out var _)) { return true; } } return false; } private static bool IsContinuousPositionTriggerMode(string triggerMode) { string text = Runtime.NormalizePositionTriggerMode(triggerMode); if (!(text == "anyplayerinside")) { return text == "allplayersinside"; } return true; } private static bool IsContinuousScanTriggerMode(string triggerMode) { string text = Runtime.NormalizeTriggerMode(triggerMode); if (!(text == "onallplayersinsidescan")) { return text == "onallplayersexitedscan"; } return true; } private static bool IsContinuousInteractTriggerMode(string targetType, string triggerMode) { string text = Runtime.NormalizeTargetType(targetType); string text2 = Runtime.NormalizeInteractionTriggerMode(triggerMode); if (!(text == "bigpickup") || (!(text2 == "onbigpickupheld") && !(text2 == "onbigpickupplaced"))) { if (text == "terminal") { if (!(text2 == "onterminalusing")) { return text2 == "onterminalexited"; } return true; } return false; } return true; } private static List<InteractTriggerRule> ReadInteractTriggerArray(JsonElement array) { //IL_04a0: Unknown result type (might be due to invalid IL or missing references) //IL_04a7: Expected O, but got Unknown List<InteractTriggerRule> list = new List<InteractTriggerRule>(); bool flag = default(bool); foreach (JsonElement item in array.EnumerateArray()) { if (item.ValueKind != JsonValueKind.Object) { continue; } InteractTriggerRule interactTriggerRule = new InteractTriggerRule { ID = GetString(item, "ID", GetString(item, "Id", GetString(item, "id", string.Empty))), Enabled = GetBool(item, "Enabled", defaultValue: true), TargetType = GetString(item, "TargetType", GetString(item, "Target", GetString(item, "ObjectType", "Any"))), TriggerMode = GetString(item, "TriggerMode", GetString(item, "Trigger", string.Empty)), Cooldown = GetFloat(item, "Cooldown", 0f), RequireInExpedition = GetBool(item, "RequireInExpedition", defaultValue: true), Index = GetInt(item, "Index", -1), InstanceID = GetInt(item, "InstanceID", GetInt(item, "ObjectInstanceID", -1)), SyncID = GetInt(item, "SyncID", GetInt(item, "SyncId", -1)), SerialNumber = GetInt(item, "SerialNumber", GetInt(item, "Serial", -1)), ItemKey = GetString(item, "ItemKey", GetString(item, "Key", string.Empty)), PublicName = GetString(item, "PublicName", GetString(item, "NameContains", string.Empty)), TerminalSerial = GetString(item, "TSL", GetString(item, "TerminalTSL", GetString(item, "TerminalTsl", GetString(item, "TerminalSelector", GetString(item, "TerminalSerial", GetString(item, "TerminalSerialNumber", GetString(item, "TerminalSerialText", GetString(item, "SerialText", GetString(item, "SerialLookup", GetString(item, "TerminalSerialLookup", string.Empty)))))))))), WorldEventObjectFilter = GetString(item, "WorldEventObjectFilter", GetString(item, "Filter", GetString(item, "ObjectFilter", string.Empty))), DataBlockID = GetUInt(item, "DataBlockID", GetUInt(item, "ItemDataBlockID", GetUInt(item, "ItemID", 0u))), ItemID = GetUInt(item, "ItemID", GetUInt(item, "PickupID", 0u)), InternalName = GetString(item, "InternalName", GetString(item, "PrefabName", string.Empty)), Radius = GetFloat(item, "Radius", 2f), UsePickupDropCycleEvents = GetBool(item, "UsePickupDropCycleEvents", GetBool(item, "UseBehaviorCycleEvents", GetBool(item, "UsePickupDropGroupEvents", defaultValue: false))), PickupDropCycleCount = Math.Max(1, GetInt(item, "PickupDropCycleCount", GetInt(item, "BehaviorCycleCount", GetInt(item, "ActionGroupCount", 3)))) }; if (item.TryGetProperty("Position", out var value) && value.ValueKind == JsonValueKind.Object) { interactTriggerRule.Position = new PositionData { x = GetFloat(value, "x", GetFloat(value, "X", 0f)), y = GetFloat(value, "y", GetFloat(value, "Y", 0f)), z = GetFloat(value, "z", GetFloat(value, "Z", 0f)) }; } if (item.TryGetProperty("Events", out var value2) && value2.ValueKind == JsonValueKind.Array) { interactTriggerRule.Events = (from e in value2.EnumerateArray() select e.Clone()).ToList(); } if (item.TryGetProperty("WardenEvents", out var value3) && value3.ValueKind == JsonValueKind.Array) { interactTriggerRule.WardenEvents = (from e in value3.EnumerateArray() select e.Clone()).ToList(); } interactTriggerRule.PickupDropCycleEvents = ReadFirstArrayProperty(item, "PickupDropCycleEvents", "PickupDropGroupEvents", "BehaviorCycleEvents", "ActionGroupEvents", "CycleEvents"); float cooldown; if (string.IsNullOrWhiteSpace(interactTriggerRule.ID)) { ManualLogSource log = Runtime.Log; if (log != null) { BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(87, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: trigger is missing required ID. File may be skipped partly. Category="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(interactTriggerRule.GetType().Name); } log.LogError(val); } } else if (TryApplyCooldownPolicy(item, "Interact", interactTriggerRule.ID, interactTriggerRule.TriggerMode, IsContinuousInteractTriggerMode(interactTriggerRule.TargetType, interactTriggerRule.TriggerMode), out cooldown)) { interactTriggerRule.Cooldown = cooldown; list.Add(interactTriggerRule); } } return list; } private static List<ScanTriggerRule> ReadScanTriggerArray(JsonElement array) { //IL_039c: Unknown result type (might be due to invalid IL or missing references) //IL_03a3: Expected O, but got Unknown List<ScanTriggerRule> list = new List<ScanTriggerRule>(); bool flag = default(bool); foreach (JsonElement item in array.EnumerateArray()) { if (item.ValueKind != JsonValueKind.Object) { continue; } ScanTriggerRule scanTriggerRule = new ScanTriggerRule { ID = GetString(item, "ID", GetString(item, "Id", GetString(item, "id", string.Empty))), Enabled = GetBool(item, "Enabled", defaultValue: true), PuzzleOverrideIndex = GetInt(item, "Index", GetInt(item, "PuzzleOverrideIndex", GetInt(item, "PuzzleIndex", -1))), TriggerMode = GetString(item, "TriggerMode", GetString(item, "Trigger", "OnScanActivated")), UsePlayerCountEvents = GetBool(item, "UsePlayerCountEvents", GetBool(item, "EnablePlayerCountEvents", defaultValue: false)), Cooldown = GetFloat(item, "Cooldown", 0f), RequireInExpedition = GetBool(item, "RequireInExpedition", defaultValue: true), RequireAlivePlayers = GetBool(item, "RequireAlivePlayers", defaultValue: true), UseTriggerCycleEvents = GetBool(item, "UseTriggerCycleEvents", GetBool(item, "UseScanCycleEvents", GetBool(item, "UseTriggerGroupEvents", defaultValue: false))), TriggerCycleCount = Math.Max(1, GetInt(item, "TriggerCycleCount", GetInt(item, "ScanCycleCount", GetInt(item, "TriggerGroupCount", 3)))) }; if (item.TryGetProperty("Events", out var value) && value.ValueKind == JsonValueKind.Array) { scanTriggerRule.Events = (from e in value.EnumerateArray() select e.Clone()).ToList(); } if (item.TryGetProperty("WardenEvents", out var value2) && value2.ValueKind == JsonValueKind.Array) { scanTriggerRule.WardenEvents = (from e in value2.EnumerateArray() select e.Clone()).ToList(); } scanTriggerRule.OnePlayerEvents = ReadFirstArrayProperty(item, "OnePlayerEvents", "EventsOnOnePlayer", "OnePlayerWardenEvents", "WardenEventsOnOnePlayer", "Player1Events", "Events1P", "Events_1P"); scanTriggerRule.TwoPlayerEvents = ReadFirstArrayProperty(item, "TwoPlayerEvents", "EventsOnTwoPlayers", "TwoPlayerWardenEvents", "WardenEventsOnTwoPlayers", "Player2Events", "Events2P", "Events_2P"); scanTriggerRule.ThreePlayerEvents = ReadFirstArrayProperty(item, "ThreePlayerEvents", "EventsOnThreePlayers", "ThreePlayerWardenEvents", "WardenEventsOnThreePlayers", "Player3Events", "Events3P", "Events_3P"); scanTriggerRule.FourPlayerEvents = ReadFirstArrayProperty(item, "FourPlayerEvents", "EventsOnFourPlayers", "FourPlayerWardenEvents", "WardenEventsOnFourPlayers", "Player4Events", "Events4P", "Events_4P"); ReadPlayerCountEventsObject(item, scanTriggerRule); scanTriggerRule.TriggerCycleEvents = ReadFirstArrayProperty(item, "TriggerCycleEvents", "ScanCycleEvents", "TriggerGroupEvents", "CycleEvents"); float cooldown; if (string.IsNullOrWhiteSpace(scanTriggerRule.ID)) { ManualLogSource log = Runtime.Log; if (log != null) { BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(87, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: trigger is missing required ID. File may be skipped partly. Category="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(scanTriggerRule.GetType().Name); } log.LogError(val); } } else if (TryApplyCooldownPolicy(item, "Scan", scanTriggerRule.ID, scanTriggerRule.TriggerMode, IsContinuousScanTriggerMode(scanTriggerRule.TriggerMode), out cooldown)) { scanTriggerRule.Cooldown = cooldown; list.Add(scanTriggerRule); } } return list; } private static List<PositionTriggerRule> ReadTriggerArray(JsonElement array) { //IL_05c6: Unknown result type (might be due to invalid IL or missing references) //IL_05cd: Expected O, but got Unknown List<PositionTriggerRule> list = new List<PositionTriggerRule>(); bool flag = default(bool); foreach (JsonElement item in array.EnumerateArray()) { if (item.ValueKind != JsonValueKind.Object) { continue; } PositionTriggerRule positionTriggerRule = new PositionTriggerRule { ID = GetString(item, "ID", GetString(item, "Id", GetString(item, "id", string.Empty))), Enabled = GetBool(item, "Enabled", defaultValue: true), TriggerAreaMode = GetString(item, "TriggerAreaMode", GetString(item, "AreaMode", GetString(item, "Mode", "Radius"))), Radius = GetFloat(item, "Radius", 3f), LocalIndex = GetInt(item, "LocalIndex", GetInt(item, "ZoneLocalIndex", -1)), Count = GetInt(item, "Count", GetInt(item, "AreaIndex", -1)), Layer = GetString(item, "Layer", string.Empty), DimensionIndex = GetInt(item, "DimensionIndex", -1), TriggerMode = GetString(item, "TriggerMode", "AnyPlayerEnter"), UsePlayerCountEvents = GetBool(item, "UsePlayerCountEvents", GetBool(item, "EnablePlayerCountEvents", GetBool(item, "UsePlayerCountEventGroups", GetBool(item, "EnablePlayerCountEventGroups", defaultValue: false)))), UseTriggerCycleEvents = GetBool(item, "UseTriggerCycleEvents", GetBool(item, "UsePositionCycleEvents", GetBool(item, "UseTriggerGroupEvents", defaultValue: false))), TriggerCycleCount = Math.Max(1, GetInt(item, "TriggerCycleCount", GetInt(item, "PositionCycleCount", GetInt(item, "TriggerGroupCount", 3)))), Cooldown = GetFloat(item, "Cooldown", 0f), RequireInExpedition = GetBool(item, "RequireInExpedition", defaultValue: true), RequireAlivePlayers = GetBool(item, "RequireAlivePlayers", defaultValue: true), IncludeBots = GetBool(item, "IncludeBots", defaultValue: true), DebugVisible = GetBool(item, "DebugVisible", defaultValue: true), DebugColor = GetString(item, "DebugColor", string.Empty) }; JsonElement value2; if (item.TryGetProperty("Position", out var value) && value.ValueKind == JsonValueKind.Object) { positionTriggerRule.Position = new PositionData { x = GetFloat(value, "x", GetFloat(value, "X", 0f)), y = GetFloat(value, "y", GetFloat(value, "Y", 0f)), z = GetFloat(value, "z", GetFloat(value, "Z", 0f)) }; } else if (item.TryGetProperty("x", out value2) || item.TryGetProperty("X", out value2) || item.TryGetProperty("y", out value2) || item.TryGetProperty("Y", out value2) || item.TryGetProperty("z", out value2) || item.TryGetProperty("Z", out value2)) { positionTriggerRule.Position = new PositionData { x = GetFloat(item, "x", GetFloat(item, "X", 0f)), y = GetFloat(item, "y", GetFloat(item, "Y", 0f)), z = GetFloat(item, "z", GetFloat(item, "Z", 0f)) }; } if (item.TryGetProperty("Events", out var value3) && value3.ValueKind == JsonValueKind.Array) { positionTriggerRule.Events = (from e in value3.EnumerateArray() select e.Clone()).ToList(); } if (item.TryGetProperty("WardenEvents", out var value4) && value4.ValueKind == JsonValueKind.Array) { positionTriggerRule.WardenEvents = (from e in value4.EnumerateArray() select e.Clone()).ToList(); } positionTriggerRule.OnePlayerEvents = ReadFirstArrayProperty(item, "OnePlayerEvents", "EventsOnOnePlayer", "OnePlayerWardenEvents", "WardenEventsOnOnePlayer", "Player1Events", "Events1P", "Events_1P"); positionTriggerRule.TwoPlayerEvents = ReadFirstArrayProperty(item, "TwoPlayerEvents", "EventsOnTwoPlayers", "TwoPlayerWardenEvents", "WardenEventsOnTwoPlayers", "Player2Events", "Events2P", "Events_2P"); positionTriggerRule.ThreePlayerEvents = ReadFirstArrayProperty(item, "ThreePlayerEvents", "EventsOnThreePlayers", "ThreePlayerWardenEvents", "WardenEventsOnThreePlayers", "Player3Events", "Events3P", "Events_3P"); positionTriggerRule.FourPlayerEvents = ReadFirstArrayProperty(item, "FourPlayerEvents", "EventsOnFourPlayers", "FourPlayerWardenEvents", "WardenEventsOnFourPlayers", "Player4Events", "Events4P", "Events_4P"); ReadPlayerCountEventsObject(item, positionTriggerRule); positionTriggerRule.TriggerCycleEvents = ReadFirstArrayProperty(item, "TriggerCycleEvents", "PositionCycleEvents", "TriggerGroupEvents", "CycleEvents"); float cooldown; if (string.IsNullOrWhiteSpace(positionTriggerRule.ID)) { ManualLogSource log = Runtime.Log; if (log != null) { BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(87, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("CTE config error: trigger is missing required ID. File may be skipped partly. Category="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(positionTriggerRule.GetType().Name); } log.LogError(val); } } else if (TryApplyCooldownPolicy(item, "Position", positionTriggerRule.ID, positionTriggerRule.TriggerMode, IsContinuousPositionTriggerMode(positionTriggerRule.TriggerMode), out cooldown)) { positionTriggerRule.Cooldown = cooldown; list.Add(positionTriggerRule); } } return list; } private static List<JsonElement> ReadFirstArrayProperty(JsonElement obj, params string[] names) { foreach (string propertyName in names) { if (obj.TryGetProperty(propertyName, out var value) && value.ValueKind == JsonValueKind.Array) { return (from e in value.EnumerateArray() select e.Clone()).ToList(); } } return new List<JsonElement>(); } private static void ReadPlayerCountEventsObject(JsonElement obj, PositionTriggerRule rule) { if (!obj.TryGetProperty("PlayerCountEvents", out var value) || value.ValueKind != JsonValueKind.Object) { return; } foreach (JsonProperty item in value.EnumerateObject()) { if (item.Value.ValueKind == JsonValueKind.Array) { List<JsonElement> list = (from e in item.Value.EnumerateArray() select e.Clone()).ToList(); switch (item.Name.Trim().ToLowerInvariant()) { case "1": case "one": case "oneplayer": rule.OnePlayerEvents = list; break; case "2": case "two": case "twoplayers": rule.TwoPlayerEvents = list; break; case "3": case "three": case "threeplayers": rule.ThreePlayerEvents = list; break; case "4": case "four": case "fourplayers": rule.FourPlayerEvents = list; break; } } } } private static void ReadPlayerCountEventsObject(JsonElement obj, ScanTriggerRule rule) { if (!obj.TryGetProperty("PlayerCountEvents", out var value) || value.ValueKind != JsonValueKind.Object) { return; } foreach (JsonProperty item in value.EnumerateObject()) { if (item.Value.ValueKind == JsonValueKind.Array) { List<JsonElement> list = (from e in item.Value.EnumerateArray() select e.Clone()).ToList(); switch (item.Name.Trim().ToLowerInvariant()) { case "1": case "one": case "oneplayer": rule.OnePlayerEvents = list; break; case "2": case "two": case "twoplayers": rule.TwoPlayerEvents = list; break; case "3": case "three": case "threeplayers": rule.ThreePlayerEvents = list; break; case "4": case "four": case "fourplayers": rule.FourPlayerEvents = list; break; } } } } private static List<JsonElement> ReadSelectorList(JsonElement value) { if (value.ValueKind == JsonValueKind.Array) { return (from e in value.EnumerateArray() select e.Clone()).ToList(); } if (value.ValueKind == JsonValueKind.String || value.ValueKind == JsonValueKind.Number || value.ValueKind == JsonValueKind.Object) { return new List<JsonElement> { value.Clone() }; } return new List<JsonElement>(); } private static DebugOptions ReadDebugOptions(JsonElement root) { DebugOptions debugOptions = new DebugOptions(); debugOptions.Enabled = GetBool(root, "EnableDebugScanMarkers", GetBool(root, "DebugScanMarkers", GetBool(root, "DebugEnabled", defaultValue: false))); debugOptions.ShowScanMarkers = GetBool(root, "ShowDebugScanMarkers", defaultValue: true); debugOptions.ShowNames = GetBool(root, "DebugShowNames", defaultValue: true); debugOptions.MarkerColor = GetString(root, "DebugMarkerColor", debugOptions.MarkerColor); debugOptions.LabelColor = GetString(root, "DebugLabelColor", debugOptions.LabelColor); debugOptions.MarkerAlpha = GetFloat(root, "DebugMarkerAlpha", debugOptions.MarkerAlpha); debugOptions.HeightOffset = GetFloat(root, "DebugHeightOffset", debugOptions.HeightOffset); debugOptions.MarkerHeight = GetFloat(root, "DebugMarkerHeight", debugOptions.MarkerHeight); debugOptions.LabelHeightOffset = GetFloat(root, "DebugLabelHeightOffset", debugOptions.LabelHeightOffset); debugOptions.RadiusScale = GetFloat(root, "DebugRadiusScale", debugOptions.RadiusScale); debugOptions.MinimumRadius = GetFloat(root, "DebugMinimumRadius", debugOptions.MinimumRadius); debugOptions.RefreshInterval = GetFloat(root, "DebugRefreshInterval", debugOptions.RefreshInterval); debugOptions.DumpRuntimeIndexes = GetBool(root, "DumpRuntimeIndexes", debugOptions.DumpRuntimeIndexes); if (root.TryGetProperty("Debug", out var value) && value.ValueKind == JsonValueKind.Object) { debugOptions.Enabled = GetBool(value, "Enabled", debugOptions.Enabled); debugOptions.ShowScanMarkers = GetBool(value, "ShowScanMarkers", debugOptions.ShowScanMarkers); debugOptions.ShowNames = GetBool(value, "ShowNames", debugOptions.ShowNames); debugOptions.MarkerColor = GetString(value, "MarkerColor", debugOptions.MarkerColor); debugOptions.LabelColor = GetString(value, "LabelColor", debugOptions.LabelColor); debugOptions.MarkerAlpha = GetFloat(value, "MarkerAlpha", debugOptions.MarkerAlpha); debugOptions.HeightOffset = GetFloat(value, "HeightOffset", debugOptions.HeightOffset); debugOptions.MarkerHeight = GetFloat(value, "MarkerHeight", debugOptions.MarkerHeight); debugOptions.LabelHeightOffset = GetFloat(value, "LabelHeightOffset", debugOptions.LabelHeightOffset); debugOptions.RadiusScale = GetFloat(value, "RadiusScale", debugOptions.RadiusScale); debugOptions.MinimumRadius = GetFloat(value, "MinimumRadius", debugOptions.MinimumRadius); debugOptions.RefreshInterval = GetFloat(value, "RefreshInterval", debugOptions.RefreshInterval); debugOptions.DumpRuntimeIndexes = GetBool(value, "DumpRuntimeIndexes", debugOptions.DumpRuntimeIndexes); } debugOptions.MarkerAlpha = Mathf.Clamp01(debugOptions.MarkerAlpha); debugOptions.RadiusScale = Math.Max(0.01f, debugOptions.RadiusScale); debugOptions.MinimumRadius = Math.Max(0.01f, debugOptions.MinimumRadius); debugOptions.MarkerHeight = Math.Max(0.001f, debugOptions.MarkerHeight); debugOptions.RefreshInterval = Math.Max(0.2f, debugOptions.RefreshInterval); return debugOptions; } private static bool GetBool(JsonElement obj, string name, bool defaultValue) { if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(name, out var value)) { if (value.ValueKind == JsonValueKind.True) { return true; } if (value.ValueKind == JsonValueKind.False) { return false; } if (value.ValueKind == JsonValueKind.String && bool.TryParse(value.GetString(), out var result)) { return result; } } return defaultValue; } private static float GetFloat(JsonElement obj, string name, float defaultValue) { if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(name, out var value)) { if (value.ValueKind == JsonValueKind.Number && value.TryGetSingle(out var value2)) { return value2; } if (value.ValueKind == JsonValueKind.String && float.TryParse(value.GetString(), out var result)) { return result; } } return defaultValue; } private static int GetInt(JsonElement obj, string name, int defaultValue) { if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(name, out var value)) { if (value.ValueKind == JsonValueKind.Number && value.TryGetInt32(out var value2)) { return value2; } if (value.ValueKind == JsonValueKind.String && int.TryParse(value.GetString(), out var result)) { return result; } } return defaultValue; } private static uint GetUInt(JsonElement obj, string name, uint defaultValue) { if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(name, out var value)) { if (value.ValueKind == JsonValueKind.Number && value.TryGetUInt32(out var value2)) { return value2; } if (value.ValueKind == JsonValueKind.String && uint.TryParse(value.GetString(), out var result)) { return result; } } return defaultValue; } private static string GetString(JsonElement obj, string name, string defaultValue) { if (obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(name, out var value)) { if (value.ValueKind == JsonValueKind.String) { return value.GetString() ?? defaultValue; } return value.ToString(); } return defaultValue; } private static string CreateChineseTemplateJson() { return "// CoordinateTriggerEvents 1.1.0 中文配置模板\n// 生成位置:<自定义Rundown数据块文件夹>/Custom/CoordinateTriggerEvents/Template_CN.json\n// 模板默认 Enabled=false,不会影响关卡。复制本文件并改名为关卡配置后,把 Enabled 改为 true。\n// 插件允许 // 注释和尾随逗号。支持多个 JSON 文件;只会启用 MainLevelLayoutIDs 匹配当前关卡的配置。\n{\n \"Enabled\": false,\n \"MainLevelLayoutIDs\":0,\n \"Debug\": {//调试模式,供模组开发者查看触发器的实际位置\n \"Enabled\": false,\n \"ShowScanMarkers\": true,\n \"ShowNames\": true,\n \"MarkerColor\": \"#00BFFF\",\n \"LabelColor\": \"#FFFFFF\",\n \"MarkerAlpha\": 0.35,\n \"HeightOffset\": 0.05,\n \"LabelHeightOffset\": 1.0\n },\n \"PositionTriggers\": [\n {\n \"ID\": \"radius_player_count_example\", //当前触发器的唯一id\n \"TriggerAreaMode\": \"Radius\",\n //Radius:按一个世界坐标点和半径生成触发范围。\n\n \"TriggerMode\": \"AnyPlayerEnter\",\n //AnyPlayerEnter 任意玩家进入触发范围触发事件\n\n //AnyPlayerInside 触发范围内有任意玩家根据\"Cooldown\": 1.0,重复触发\n\n //AllPlayersEnter 所有符合条件的玩家进入范围后触发\n\n //AllPlayersInside 所有符合条件的玩家根据\"Cooldown\": 1.0,重复触发\n\n //AnyPlayerExit 任意玩家从范围内离开范围时触发\n\n //AllPlayersExit 所有玩家从范围内离开时触发\n\n \"Enabled\": true,\n \"Position\": {\n \"x\": 0,\n \"y\": 0,\n \"z\": 0\n },\n \"Radius\": 5.0, //触发器大小\n \"Cooldown\": 1.0, //内置冷却cd\n \"RequireAlivePlayers\": true, //玩家倒地不计算为人数\n \"DebugVisible\": true, //开启调试模式\n \"Events\": [], //事件列表\n \"UsePlayerCountEvents\": false,\n // true 时只执行 PlayerCountEvents 中对应人数的事件组;false 时执行 Events内的事件组\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n // 每成功触发一次+1Count;累计 TriggerCycleCount 组后额外触发事件\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"whole_zone_localindex_0\",\n \"TriggerAreaMode\": \"OverrideBigZone\",\n //OverrideBigZone 生成一个覆盖指定ZONE的触发器\n \"TriggerMode\": \"AnyPlayerEnter\",\n \"Enabled\": true,\n \"DimensionIndex\": 0,\n \"Layer\": 0,\n \"LocalIndex\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"DebugVisible\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"whole_area_count_0\",\n \"TriggerAreaMode\": \"OverrideArea\",\n //OverrideArea 生成一个覆盖指定Zone Area区域的触发器\n \"TriggerMode\": \"AnyPlayerEnter\",\n \"Enabled\": true,\n \"DimensionIndex\": 0,\n \"Layer\":0,\n \"LocalIndex\": 0,\n \"Count\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"DebugVisible\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n }\n ],\n \"ScanTriggers\": [\n {\n \"ID\": \"scan_1_activated_player_count\",\n \"TriggerMode\": \"OnScanActivated\",\n //OnScanActivated 玩家激活扫描点时触发事件\n //OnPlayerExitScan 玩家退出扫描点时触发事件\n \"Enabled\": true,\n // Index 使用 ScanPositionOverride / ECC 在 BepInEx 日志中输出的 PuzzleOverrideIndex,索引从 1 开始。\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"scan_1_player_exit_event\",\n \"TriggerMode\": \"OnPlayerExitScan\",\n \"Enabled\": true,\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"scan_1_all_players_enter_once\",\n \"TriggerMode\": \"OnAllPlayersEnterScan\",\n \"Enabled\": true,\n // 全员进入扫描点时触发一次。\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"scan_1_all_players_inside_repeat\",\n \"TriggerMode\": \"OnAllPlayersInsideScan\",\n \"Enabled\": true,\n // 全员持续在扫描点内时按 Cooldown 重复触发。\n \"Index\": 0,\n \"Cooldown\": 5.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"scan_1_all_players_exit_once\",\n \"TriggerMode\": \"OnAllPlayersExitScan\",\n \"Enabled\": true,\n // 全员曾进入扫描点后全部退出时触发一次。\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"scan_1_all_players_exited_repeat\",\n \"TriggerMode\": \"OnAllPlayersExitedScan\",\n \"Enabled\": true,\n // 全员曾进入扫描点并全部退出后按 Cooldown 重复触发;初始无人不会触发。\n \"Index\": 0,\n \"Cooldown\": 5.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n }\n ],\n \"InteractTriggers\": [\n {\n \"ID\": \"bigpickup_1_pickup_event\",\n \"TargetType\": \"BigPickup\",\n \"TriggerMode\": \"OnBigPickupPickup\",\n //OnBigPickupPickup 拾取大物品触发事件\n //OnBigPickupDrop 放下大物品触发事件\n \"Enabled\": true,\n // Index 使用 ScanPositionOverride / ECC 在 BepInEx 日志中输出的 BigPickup Item Index,索引从 1 开始。\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"Events\": [],\n \"UsePickupDropCycleEvents\": false,\n \"PickupDropCycleCount\": 0,\n \"PickupDropCycleEvents\": []\n },\n {\n \"ID\": \"bigpickup_1_drop_event\",\n \"TargetType\": \"BigPickup\",\n \"TriggerMode\": \"OnBigPickupDrop\",\n \"Enabled\": true,\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"Events\": []\n },\n {\n \"ID\": \"bigpickup_1_held_repeat_event\",\n \"TargetType\": \"BigPickup\",\n \"TriggerMode\": \"OnBigPickupHeld\",\n \"Enabled\": true,\n \"Index\": 0,\n // 玩家拿着该大物品期间按 Cooldown 重复触发,类似 AnyPlayerInside。\n \"Cooldown\": 5.0,\n \"Events\": []\n },\n {\n \"ID\": \"bigpickup_1_placed_repeat_event\",\n \"TargetType\": \"BigPickup\",\n \"TriggerMode\": \"OnBigPickupPlaced\",\n \"Enabled\": true,\n \"Index\": 0,\n // 玩家放下该大物品后,只要大物品保持放置状态,就按 Cooldown 重复触发。\n \"Cooldown\": 5.0,\n \"Events\": []\n },\n {\n \"ID\": \"terminal_tsl_use_event\",\n \"TargetType\": \"Terminal\",\n \"TriggerMode\": \"OnTerminalUse\",\n //OnTerminalUse 使用终端触发事件\n //OnTerminalUnused 退出终端触发事件\n \"Enabled\": true,\n // TerminalSelector 与 AWO TSL 一致:[TERMINAL_DimensionIndex_LayerIndex_ZoneLocalIndex_TerminalIndexInZone]\n \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n \"Cooldown\": 1.0,\n \"Events\": []\n },\n {\n \"ID\": \"terminal_tsl_unused_event\",\n \"TargetType\": \"Terminal\",\n \"TriggerMode\": \"OnTerminalUnused\",\n \"Enabled\": true,\n \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n \"Cooldown\": 1.0,\n \"Events\": []\n },\n {\n \"ID\": \"terminal_tsl_using_repeat_event\",\n \"TargetType\": \"Terminal\",\n \"TriggerMode\": \"OnTerminalUsing\",\n \"Enabled\": true,\n \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n // 玩家正在使用该终端期间按 Cooldown 重复触发,类似 AnyPlayerInside。\n \"Cooldown\": 5.0,\n \"Events\": []\n },\n {\n \"ID\": \"terminal_tsl_exited_repeat_event\",\n \"TargetType\": \"Terminal\",\n \"TriggerMode\": \"OnTerminalExited\",\n \"Enabled\": true,\n \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n // 必须玩家使用过一次该终端并退出后,才会按 Cooldown 重复触发;初始未使用状态不会触发。\n \"Cooldown\": 5.0,\n \"Events\": []\n }\n ]\n}"; } private static string CreateEnglishTemplateJson() { return "// CoordinateTriggerEvents 1.1.0 English configuration template\n// Generated path: <Custom Rundown datablock folder>/Custom/CoordinateTriggerEvents/Template_EN.json\n// The template defaults to Enabled=false and will not affect levels. Copy this file, rename it as a level config, then set Enabled to true.\n// The plugin allows // comments and trailing commas. Multiple JSON files are supported; only configs whose MainLevelLayoutIDs match the current level are enabled.\n{\n \"Enabled\": false,\n \"MainLevelLayoutIDs\":0,\n \"Debug\": {//Debug mode, for mod developers to view the actual positions of triggers\n \"Enabled\": false,\n \"ShowScanMarkers\": true,\n \"ShowNames\": true,\n \"MarkerColor\": \"#00BFFF\",\n \"LabelColor\": \"#FFFFFF\",\n \"MarkerAlpha\": 0.35,\n \"HeightOffset\": 0.05,\n \"LabelHeightOffset\": 1.0\n },\n \"PositionTriggers\": [\n {\n \"ID\": \"radius_player_count_example\", //Unique ID of the current trigger\n \"TriggerAreaMode\": \"Radius\",\n //Radius: generates a trigger area from a world position and radius.\n\n \"TriggerMode\": \"AnyPlayerEnter\",\n //AnyPlayerEnter Triggers the event when any player enters the trigger area\n\n //AnyPlayerInside Repeatedly triggers while any player is inside the trigger area according to \"Cooldown\": 1.0,\n\n //AllPlayersEnter Triggers after all eligible players enter the area\n\n //AllPlayersInside Repeatedly triggers while all eligible players are inside according to \"Cooldown\": 1.0,\n\n //AnyPlayerExit Triggers when any player leaves the area from inside\n\n //AllPlayersExit Triggers when all players leave the area from inside\n\n \"Enabled\": true,\n \"Position\": {\n \"x\": 0,\n \"y\": 0,\n \"z\": 0\n },\n \"Radius\": 5.0, //Trigger size\n \"Cooldown\": 1.0, //Built-in cooldown\n \"RequireAlivePlayers\": true, //Downed players are not counted as player count\n \"DebugVisible\": true, //Enable debug mode\n \"Events\": [], //Event list\n \"UsePlayerCountEvents\": false,\n // When true, only executes the event group matching the player count in PlayerCountEvents; when false, executes the event group inside Events\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n // Adds 1 Count for every successful trigger; after accumulating TriggerCycleCount groups, triggers additional events\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"whole_zone_localindex_0\",\n \"TriggerAreaMode\": \"OverrideBigZone\",\n //OverrideBigZone generates a trigger that covers the specified ZONE\n \"TriggerMode\": \"AnyPlayerEnter\",\n \"Enabled\": true,\n \"DimensionIndex\": 0,\n \"Layer\": 0,\n \"LocalIndex\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"DebugVisible\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"whole_area_count_0\",\n \"TriggerAreaMode\": \"OverrideArea\",\n //OverrideArea generates a trigger that covers the specified Zone Area\n \"TriggerMode\": \"AnyPlayerEnter\",\n \"Enabled\": true,\n \"DimensionIndex\": 0,\n \"Layer\":0,\n \"LocalIndex\": 0,\n \"Count\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"DebugVisible\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n }\n ],\n \"ScanTriggers\": [\n {\n \"ID\": \"scan_1_activated_player_count\",\n \"TriggerMode\": \"OnScanActivated\",\n //OnScanActivated Triggers the event when players activate the scan\n //OnPlayerExitScan Triggers the event when a player exits the scan\n \"Enabled\": true,\n // Index uses the PuzzleOverrideIndex printed by ScanPositionOverride / ECC in the BepInEx log. The index starts from 1.\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"scan_1_player_exit_event\",\n \"TriggerMode\": \"OnPlayerExitScan\",\n \"Enabled\": true,\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"scan_1_all_players_enter_once\",\n \"TriggerMode\": \"OnAllPlayersEnterScan\",\n \"Enabled\": true,\n // Triggers once when all players enter the scan.\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"scan_1_all_players_inside_repeat\",\n \"TriggerMode\": \"OnAllPlayersInsideScan\",\n \"Enabled\": true,\n // Repeatedly triggers by Cooldown while all players continuously stay inside the scan.\n \"Index\": 0,\n \"Cooldown\": 5.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"scan_1_all_players_exit_once\",\n \"TriggerMode\": \"OnAllPlayersExitScan\",\n \"Enabled\": true,\n // Triggers once after all players have entered the scan and then all leave it.\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n },\n {\n \"ID\": \"scan_1_all_players_exited_repeat\",\n \"TriggerMode\": \"OnAllPlayersExitedScan\",\n \"Enabled\": true,\n // Repeatedly triggers by Cooldown after all players have entered the scan and then all leave it; it does not trigger when the scan starts empty.\n \"Index\": 0,\n \"Cooldown\": 5.0,\n \"RequireAlivePlayers\": true,\n \"Events\": [],\n \"UsePlayerCountEvents\": false,\n \"PlayerCountEvents\": {\n \"1\": [],\n \"2\": [],\n \"3\": [],\n \"4\": []\n },\n \"UseTriggerCycleEvents\": false,\n \"TriggerCycleCount\": 0,\n \"TriggerCycleEvents\": []\n }\n ],\n \"InteractTriggers\": [\n {\n \"ID\": \"bigpickup_1_pickup_event\",\n \"TargetType\": \"BigPickup\",\n \"TriggerMode\": \"OnBigPickupPickup\",\n //OnBigPickupPickup Triggers the event when a big pickup is picked up\n //OnBigPickupDrop Triggers the event when a big pickup is dropped\n \"Enabled\": true,\n // Index uses the BigPickup Item Index printed by ScanPositionOverride / ECC in the BepInEx log. The index starts from 1.\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"Events\": [],\n \"UsePickupDropCycleEvents\": false,\n \"PickupDropCycleCount\": 0,\n \"PickupDropCycleEvents\": []\n },\n {\n \"ID\": \"bigpickup_1_drop_event\",\n \"TargetType\": \"BigPickup\",\n \"TriggerMode\": \"OnBigPickupDrop\",\n \"Enabled\": true,\n \"Index\": 0,\n \"Cooldown\": 1.0,\n \"Events\": []\n },\n {\n \"ID\": \"bigpickup_1_held_repeat_event\",\n \"TargetType\": \"BigPickup\",\n \"TriggerMode\": \"OnBigPickupHeld\",\n \"Enabled\": true,\n \"Index\": 0,\n // Repeatedly triggers by Cooldown while the player is carrying this big pickup, similar to AnyPlayerInside.\n \"Cooldown\": 5.0,\n \"Events\": []\n },\n {\n \"ID\": \"bigpickup_1_placed_repeat_event\",\n \"TargetType\": \"BigPickup\",\n \"TriggerMode\": \"OnBigPickupPlaced\",\n \"Enabled\": true,\n \"Index\": 0,\n // After the player drops this big pickup, repeatedly triggers by Cooldown while the big pickup remains placed.\n \"Cooldown\": 5.0,\n \"Events\": []\n },\n {\n \"ID\": \"terminal_tsl_use_event\",\n \"TargetType\": \"Terminal\",\n \"TriggerMode\": \"OnTerminalUse\",\n //OnTerminalUse Triggers the event when using a terminal\n //OnTerminalUnused Triggers the event when leaving a terminal\n \"Enabled\": true,\n // TerminalSelector is the same as AWO TSL: [TERMINAL_DimensionIndex_LayerIndex_ZoneLocalIndex_TerminalIndexInZone]\n \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n \"Cooldown\": 1.0,\n \"Events\": []\n },\n {\n \"ID\": \"terminal_tsl_unused_event\",\n \"TargetType\": \"Terminal\",\n \"TriggerMode\": \"OnTerminalUnused\",\n \"Enabled\": true,\n \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n \"Cooldown\": 1.0,\n \"Events\": []\n },\n {\n \"ID\": \"terminal_tsl_using_repeat_event\",\n \"TargetType\": \"Terminal\",\n \"TriggerMode\": \"OnTerminalUsing\",\n \"Enabled\": true,\n \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n // Repeatedly triggers by Cooldown while the player is using this terminal, similar to AnyPlayerInside.\n \"Cooldown\": 5.0,\n \"Events\": []\n },\n {\n \"ID\": \"terminal_tsl_exited_repeat_event\",\n \"TargetType\": \"Terminal\",\n \"TriggerMode\": \"OnTerminalExited\",\n \"Enabled\": true,\n \"TerminalSelector\": \"[TERMINAL_0_0_0_0]\",\n // The player must have used and then exited this terminal once before this repeatedly triggers by Cooldown; it will not trigger in the initial unused state.\n \"Cooldown\": 5.0,\n \"Events\": []\n }\n ]\n}"; } } internal static class Runtime { private sealed class ZoneLookupCacheEntry { public readonly List<LG_Zone> Zones = new List<LG_Zone>(); public readonly List<(LG_Zone Zone, object Area, int Index)> Areas = new List<(LG_Zone, object, int)>(); public bool Resolved; public float LastAttemptTime = -999999f; public string LastFailure = string.Empty; } private sealed class PendingConfiguredEvent { public JsonElement EventElement; public string OwnerLabel = string.Empty; public float DueTime; } private readonly struct TerminalTslAddress { public readonly int DimensionIndex; public readonly int LayerIndex; public readonly int ZoneLocalIndex; public readonly int TerminalIndexInZone; public TerminalTslAddress(int dimensionIndex, int layerIndex, int zoneLocalIndex, int terminalIndexInZone) { DimensionIndex = dimensionIndex; LayerIndex = layerIndex; ZoneLocalIndex = zoneLocalIndex; TerminalIndexInZone = terminalIndexInZone; } public string ToToken() { return $"TERMINAL_{DimensionIndex}_{LayerIndex}_{ZoneLocalIndex}_{TerminalIndexInZone}"; } public string ToBracketedToken() { return "[" + ToToken() + "]"; } public bool Matches(TerminalTslAddress other) { if (DimensionIndex == other.DimensionIndex && LayerIndex == other.LayerIndex && ZoneLocalIndex == other.ZoneLocalIndex) { return TerminalIndexInZone == other.TerminalIndexInZone; } return false; } } internal static ManualLogSource? Log; internal static readonly bool VerboseConsoleLogging = false; private static readonly Dictionary<string, TriggerState> States = new Dictionary<string, TriggerState>(StringComparer.OrdinalIgnoreCase); private static readonly List<LocalizedText> LocalizedTextRoots = new List<LocalizedText>(); private static float _lastTickTime; private static float _lastLogTime; private static readonly Dictionary<string, float> LastLogTimesByMessage = new Dictionary<string, float>(StringComparer.Ordinal); private static readonly Dictionary<string, string> PositionModeNormalizationCache = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly Dictionary<string, string> ScanModeNormalizationCache = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly Dictionary<string, string> InteractModeNormalizationCache = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly Dictionary<string, string> TargetTypeNormalizationCache = new Dictionary<string, string>(StringComparer.Ordinal); private const int NormalizationCacheLimit = 512; private static readonly Dictionary<string, uint> PartialDataPersistentIdCache = new Dictionary<string, uint>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, uint> LevelLayoutStringIdCache = new Dictionary<string, uint>(StringComparer.OrdinalIgnoreCase); private static Dictionary<string, uint>? PersistentIdDumpCache; private static readonly Dictionary<int, HashSet<string>> TerminalSelectorCache = new Dictionary<int, HashSet<string>>(); private static readonly Dictionary<int, TerminalTslAddress> TerminalTslAddressCache = new Dictionary<int, TerminalTslAddress>(); private static readonly Dictionary<string, bool> TerminalSelectorMatchCache = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<int> TerminalSelectorCacheMisses = new HashSet<int>(); private static bool TerminalSelectorCacheWarmupComplete; private static float LastTerminalSelectorCacheWarmupAttempt = -999999f; private static readonly Dictionary<string, ZoneLookupCacheEntry> ZoneLookupCache = new Dictionary<string, ZoneLookupCacheEntry>(StringComparer.OrdinalIgnoreCase); private static readonly object ActiveTriggerCacheLock = new object(); private static readonly List<(ConfigDocument Config, PositionTriggerRule Trigger)> ActivePositionTriggerCache = new List<(ConfigDocument, PositionTriggerRule)>(); private static readonly List<(ConfigDocument Config, ScanTriggerRule Trigger)> ActiveScanTriggerCache = new List<(ConfigDocument, ScanTriggerRule)>(); private static readonly List<(ConfigDocument Config, InteractTriggerRule Trigger)> ActiveInteractTriggerCache = new List<(ConfigDocument, InteractTriggerRule)>(); private static bool ActiveTriggerCacheDirty = true; private static uint ActiveTriggerCacheLayoutId = uint.MaxValue; private static string ActiveTriggerCacheLayoutName = string.Empty; private static readonly Queue<PendingConfiguredEvent> PendingConfiguredEvents = new Queue<PendingConfiguredEvent>(); private const int MaxQueuedConfiguredEventsPerTick = 3; internal static void LogVerbose(string message) { if (VerboseConsoleLogging) { ManualLogSource? log = Log; if (log != null) { log.LogInfo((object)message); } } } internal static void MarkActiveTriggerCacheDirty() { lock (ActiveTriggerCacheLock) { ActiveTriggerCacheDirty = true; } } internal static void ClearConfigurationResolutionCaches() { PartialDataPersistentIdCache.Clear(); LevelLayoutStringIdCache.Clear(); PersistentIdDumpCache = null; } private static string GetCachedNormalization(Dictionary<string, string> cache, string? text, Func<string, string> normalize) { string text2 = text ?? string.Empty; if (cache.TryGetValue(text2, out string value) && value != null) { return value; } string text3 = normalize(text2); if (cache.Count < 512) { cache[text2] = text3; } return text3; } private static void ClearHotPathRuntimeCaches() { if (PositionModeNormalizationCache.Count > 512) { PositionModeNormalizationCache.Clear(); } if (ScanModeNormalizationCache.Count > 512) { ScanModeNormalizationCache.Clear(); } if (InteractModeNormalizationCache.Count > 512) { InteractModeNormalizationCache.Clear(); } if (TargetTypeNormalizationCache.Count > 512) { TargetTypeNormalizationCache.Clear(); } } private static void EnsureActiveTriggerCache() { uint currentLevelLayoutId = GetCurrentLevelLayoutId(); string text = TryGetLevelLayoutName(currentLevelLayoutId); lock (ActiveTriggerCacheLock) { if (!ActiveTriggerCacheDirty && ActiveTriggerCacheLayoutId == currentLevelLayoutId && string.Equals(ActiveTriggerCacheLayoutName, text, StringComparison.OrdinalIgnoreCase)) { return; } ActivePositionTriggerCache.Clear(); ActiveScanTriggerCache.Clear(); ActiveInteractTriggerCache.Clear(); foreach (ConfigDocument config in ConfigManager.Configs) { if (!config.Enabled || !MatchesCurrentLevel(config, currentLevelLayoutId, text, out string _)) { continue; } foreach (PositionTriggerRule positionTrigger in config.PositionTriggers) { if (positionTrigger.Enabled) { ActivePositionTriggerCache.Add((config, positionTrigger)); } } foreach (ScanTriggerRule scanTrigger in config.ScanTriggers) { if (scanTrigger.Enabled) { ActiveScanTriggerCache.Add((config, scanTrigger)); } } foreach (InteractTriggerRule interactTrigger in config.InteractTriggers) { if (interactTrigger.Enabled) { ActiveInteractTriggerCache.Add((config, interactTrigger)); } } } ActiveTriggerCacheLayoutId = currentLevelLayoutId; ActiveTriggerCacheLayoutName = text; ActiveTriggerCacheDirty = false; } } internal static void OnExpeditionStarted() { States.Clear(); LocalizedTextRoots.Clear(); ZoneLookupCache.Clear(); LastLogTimesByMessage.Clear(); PendingConfiguredEvents.Clear(); ClearHotPathRuntimeCaches(); ClearTerminalSelectorCache(); ConfigManager.LoadOrCreate(Log, force: true); MarkActiveTriggerCacheDirty(); EnsureActiveTriggerCache(); uint currentLevelLayoutId = GetCurrentLevelLayoutId(); string value = TryGetLevelLayoutName(currentLevelLayoutId); int count = GetActiveTriggers().Count; int count2 = GetActiveScanTriggers().Count; int count3 = GetActiveInteractTriggers().Count; ScanTriggerManager.Reset(); InteractTriggerManager.Reset(); LogVerbose($"Expedition started. LevelLayoutID={currentLevelLayoutId}, LevelLayoutName='{value}', active coordinate triggers={count}, active scan triggers={count2}, active interact triggers={count3}, configs={ConfigManager.ConfigPathSummary}"); } internal static void Tick() { try { if (Time.realtimeSinceStartup - _lastTickTime < 0.2f) { return; } _lastTickTime = Time.realtimeSinceStartup; ConfigManager.ProcessQueuedReload(Log); if (!GameStateManager.IsInExpedition) { DebugMarkerManager.Clear(); return; } if (ShouldDumpRuntimeBindings()) { ScanTriggerManager.DumpScanIndexesIfNeeded(); InteractTriggerManager.DumpTargetIndexesIfNeeded(); } WarmTerminalSelectorCacheIfNeeded(); ProcessQueuedConfiguredEvents(); InteractTriggerManager.ProcessPendingTerminalEvents(); InteractTriggerManager.ProcessTerminalRepeatEvents(); InteractTriggerManager.ProcessBigPickupRepeatEvents(); ScanTriggerManager.ProcessScanRepeatEvents(); List<(ConfigDocument, PositionTriggerRule)> activeTriggers = GetActiveTriggers(); DebugMarkerManager.UpdateMarkers(activeTriggers); if (activeTriggers.Count == 0) { return; } foreach (var (config, trigger) in activeTriggers) { EvaluateTrigger(config, trigger); } } catch (Exception ex) { LogThrottled("Tick failed: " + ex.GetType().Name + ": " + ex.Message); } } private static List<(ConfigDocument Config, PositionTriggerRule Trigger)> GetActiveTriggers() { EnsureActiveTriggerCache(); return ActivePositionTriggerCache; } internal static List<(ConfigDocument Config, ScanTriggerRule Trigger)> GetActiveScanTriggers() { EnsureActiveTriggerCache(); return ActiveScanTriggerCache; } internal static List<(ConfigDocument Config, InteractTriggerRule Trigger)> GetActiveInteractTriggers() { EnsureActiveTriggerCache(); return ActiveInteractTriggerCache; } private static bool ShouldDumpRuntimeBindings() { return ConfigManager.ShouldDumpRuntimeIndexes(); } private static int QueueConfiguredEventList(IEnumerable<JsonElement> events, string ownerLabel, float delaySeconds = 0.05f) { int num = 0; float dueTime = Time.realtimeSinceStartup + Math.Max(0f, delaySeconds); foreach (JsonElement @event in events) { PendingConfiguredEvents.Enqueue(new PendingConfiguredEvent { EventElement = @event.Clone(), OwnerLabel = ownerLabel, DueTime = dueTime }); num++; } return num; } private static void ProcessQueuedConfiguredEvents() { if (PendingConfiguredEvents.Count == 0) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; int num = 0; while (PendingConfiguredEvents.Count > 0 && num < 3) { PendingConfiguredEvent pendingConfiguredEvent = PendingConfiguredEvents.Peek(); if (!(pendingConfiguredEvent.DueTime > realtimeSinceStartup)) { PendingConfiguredEvents.Dequeue(); TryExecuteConfiguredEvent(pendingConfiguredEvent.EventElement, pendingConfiguredEvent.OwnerLabel); num++; continue; } break; } } internal static void FireInteractTrigger(string sourceKind, string eventName, Component? source, PlayerAgent? player, string sourceName, string stateKeySuffix) { int firedDispatchCount = InteractTriggerManager.FiredDispatchCount; foreach (var (config, trigger) in GetActiveInteractTriggers()) { FireInteractTrigger(config, trigger, sourceKind, eventName, source, player, sourceName, stateKeySuffix); } if (InteractTriggerManager.FiredDispatchCount == firedDispatchCount && (NormalizeTargetType(sourceKind) == "bigpickup" || NormalizeTargetType(sourceKind) == "terminal")) { LogVerbose($"No interact trigger matched. Event={eventName}, TargetType={sourceKind}, Source={sourceName}. Check Index/SerialNumber/ItemKey/TerminalSelector in config."); } } internal static void FireBigPickupCycleEvents(Component source, PlayerAgent? player, string sourceName, int completedCycles) { foreach (var (configDocument, interactTriggerRule) in GetActiveInteractTriggers()) { if (!interactTriggerRule.UsePickupDropCycleEvents || interactTriggerRule.PickupDropCycleEvents.Count == 0 || (interactTriggerRule.RequireInExpedition && !GameStateManager.IsInExpedition) || !InteractTriggerMatches(interactTriggerRule, "BigPickup", "OnBigPickupDrop", source, ignoreTriggerMode: true)) { continue; } int num = Math.Max(1, interactTriggerRule.PickupDropCycleCount); if (completedCycles % num == 0) { string key = configDocument.FilePath + "::interact-cycle::" + interactTriggerRule.ID + "::" + InteractTriggerManager.GetRuntimeIndex("BigPickup", source) + "::" + completedCycles; if (!InteractTriggerManager.RuleStates.TryGetValue(key, out TriggerState value)) { value = new TriggerState(); InteractTriggerManager.RuleStates[key] = value; } if (!(interactTriggerRule.Cooldown > 0f) || !(Time.realtimeSinceStartup - value.LastFireTime < interactTriggerRule.Cooldown)) { value.LastFireTime = Time.realtimeSinceStartup; value.Fired = true; int value2 = ExecuteEventList(interactTriggerRule.PickupDropCycleEvents, $"BigPickup pickup/drop cycle trigger '{interactTriggerRule.ID}' cycles={completedCycles}"); LogVerbose($"BigPickup pickup/drop cycle trigger '{interactTriggerRule.ID}' fired. Cycles={completedCycles}, Required={num}, Source={sourceName}, ExecutedEvents={value2}"); } } } } private static void FireInteractTrigger(ConfigDocument config, InteractTriggerRule trigger, string sourceKind, string eventName, Component? source, PlayerAgent? player, string sourceName, string stateKeySuffix) { if ((trigger.RequireInExpedition && !GameStateManager.IsInExpedition) || !InteractTriggerMatches(trigger, sourceKind, eventName, source)) { return; } string key = config.FilePath + "::interact::" + trigger.ID + "::" + stateKeySuffix; if (!InteractTriggerManager.RuleStates.TryGetValue(key, out TriggerState value)) { value = new TriggerState(); InteractTriggerManager.RuleStates[key] = value; } if (trigger.Cooldown > 0f && Time.realtimeSinceStartup - value.LastFireTime < trigger.Cooldown) { return; } value.Fired = true; value.LastFireTime = Time.realtimeSinceStartup; int num = 0; IEnumerable<JsonElement> enumerable = trigger.Events.Concat(trigger.WardenEvents); bool flag = NormalizeTargetType(sourceKind) == "terminal" && (NormalizeInteractionTriggerMode(eventName) == "onterminaluse" || NormalizeInteractionTriggerMode(eventName) == "onterminalunused"); if (flag) { num = QueueConfiguredEventList(enumerable, "Interact trigger '" + trigger.ID + "'"); } else { foreach (JsonElement item in enumerable) { if (TryExecuteConfiguredEvent(item, "Interact trigger '" + trigger.ID + "'")) { num++; } } } InteractTriggerManager.FiredDispatchCount++; LogVerbose($"Interact trigger '{trigger.ID}' fired. Event={eventName}, TargetType={sourceKind}, Source={sourceName}, {(flag ? "QueuedEvents" : "ExecutedEvents")}={num}"); } private static bool InteractTriggerMatches(InteractTriggerRule trigger, string sourceKind, string eventName, Component? source, bool ignoreTriggerMode = false) { //IL_03fb: Unknown result type (might be due to invalid IL or missing references) //IL_0400: Unknown result type (might be due to invalid IL or missing references) //IL_0410: Unknown result type (might be due to invalid IL or missing references) //IL_0415: Unknown result type (might be due to invalid IL or missing references) //IL_0429: Unknown result type (might be due to invalid IL or missing references) //IL_042b: Unknown result type (might be due to invalid IL or missing references) //IL_042d: Unknown result type (might be due to invalid IL or missing references) //IL_0432: Unknown result type (might be due to invalid IL or missing references) string text = NormalizeTargetType(trigger.TargetType); string text2 = NormalizeTargetType(sourceKind); if (!string.IsNullOrWhiteSpace(text) && text != "any" && text != text2) { return false; } if (!ignoreTriggerMode) { string text3 = NormalizeInteractionTriggerMode(trigger.TriggerMode); string text4 = NormalizeInteractionTriggerMode(eventName); if (!string.IsNullOrWhiteSpace(text3) && text3 != text4) { return false; } } if ((Object)(object)source == (Object)null) { return true; } if (trigger.Index < 0 && trigger.InstanceID < 0 && trigger.SyncID < 0 && trigger.SerialNumber < 0 && trigger.DataBlockID == 0 && trigger.ItemID == 0 && string.IsNullOrWhiteSpace(trigger.ItemKey) && string.IsNullOrWhiteSpace(trigger.PublicName) && string.IsNullOrWhiteSpace(trigger.TerminalSerial) && string.IsNullOrWhiteSpace(trigger.WorldEventObjectFilter) && string.IsNullOrWhiteSpace(trigger.InternalName) && trigger.Position == null) { return true; } if (trigger.Index >= 0) { if (text2 == "bigpickup") { if (trigger.Index <= 0) { LogThrottled($"BigPickup trigger '{trigger.ID}' uses invalid Index={trigger.Index}. ScanPosOverride/ECC BigPickup item indices start at 1."); return false; } if (!SpoIndexResolver.TryGetBigPickupSpoIndex(source, out int index, out string _)) { LogThrottled($"BigPickup trigger '{trigger.ID}' cannot match: SPO/ECC BigPickup Item Index is unavailable. ConfigIndex={trigger.Index}, Source={((source != null) ? ((Object)source).name : null) ?? "<null>"}"); return false; } if (index != trigger.Index) { return false; } } else { int index = InteractTriggerManager.GetRuntimeIndex(text2, source); if (index < 0) { index = TryGetIntMember(source, "Index", "m_index", "m_terminalIndex", "m_terminalSerialIndex", "PuzzleOverrideIndex"); } if (index != trigger.Index) { return false; } } } if (!string.IsNullOrWhiteSpace(trigger.WorldEventObjectFilter) && !ObjectMatchesFilter(source, trigger.WorldEventObjectFilter)) { return false; } if (!string.IsNullOrWhiteSpace(trigger.InternalName) && TryGetPublicObjectName(source).IndexOf(trigger.InternalName, StringComparison.OrdinalIgnoreCase) < 0) { return false; } if (trigger.DataBlockID != 0 && TryGetUIntMember(source, "DataBlockID", "ItemDataBlockID", "m_dataBlockID", "m_itemDataBlockID", "ItemID", "m_itemID") != trigger.DataBlockID) { return false; } if (trigger.InstanceID >= 0) { int num = -1; try { num = ((Object)source).GetInstanceID(); } catch { } if (num != trigger.InstanceID) { return false; } } if (trigger.SyncID >= 0 && TryGetIntMember(source, "SyncID", "m_syncID") != trigger.SyncID) { return false; } if (trigger.SerialNumber >= 0 && TryGetIntMember(source, "m_serialNumber", "SerialNumber", "m_serial") != trigger.SerialNumber) { return false; } if (!string.IsNullOrWhiteSpace(trigger.ItemKey) && !string.Equals(TryGetStringMember(source, "m_itemKey", "ItemKey", "Key"), trigger.ItemKey, StringComparison.OrdinalIgnoreCase)) { return false; } if (!string.IsNullOrWhiteSpace(trigger.TerminalSerial)) { if (text2 != "terminal") { return false; } if (!TerminalMatchesSelector(source, trigger.TerminalSerial)) { return false; } } if (!string.IsNullOrWhiteSpace(trigger.PublicName) && TryGetPublicObjectName(source).IndexOf(trigger.PublicName, StringComparison.OrdinalIgnoreCase) < 0) { return false; } if (trigger.Position != null) { Vector3 position; try { position = source.transform.position; } catch { return false; } Vector3 val = trigger.Position.ToVector3(); float num2 = Math.Max(0.01f, trigger.Radius); Vector3 val2 = position - val; if (((Vector3)(ref val2)).sqrMagnitude > num2 * num2) { return false; } } return true; } internal static string NormalizeTargetType(string text) { return GetCachedNormalization(TargetTypeNormalizationCache, text, (string value) => string.IsNullOrWhiteSpace(value) ? "any" : (value.Trim().Replace("_", string.Empty).Replace("-", string.Empty) .Replace(" ", string.Empty) .ToLowerInvariant() switch { "terminal" => "terminal", "computerterminal" => "terminal", "interact" => "interact", "interaction" => "interact", "position" => "position", "coordinate" => "coordinate", "scan" => "scan", "bioscan" => "bioscan", "bigpickup" => "bigpickup", "largepickup" => "bigpickup", "carryitem" => "bigpickup", "carryitempickup" => "bigpickup", "any" => "any", "all" => "any", _ => value.Trim().ToLowerInvariant(), })); } internal static string NormalizeInteractionTriggerMode(string text) { return GetCachedNormalization(InteractModeNormalizationCache, text, (string value) => string.IsNullOrWhiteSpace(value) ? string.Empty : (value.Trim().Replace("_", string.Empty).Replace("-", string.Empty) .Replace(" ", string.Empty) .ToLowerInvariant() switch { "pickup" => "onbigpickuppickup", "pickedup" => "onbigpickuppickup", "onpickup" => "onbigpickuppickup", "onpickedup" => "onbigpickuppickup", "bigpickuppickup" => "onbigpickuppickup", "onbigpickuppickup" => "onbigpickuppickup", "bigpickuppickedup" => "onbigpickuppickup", "onbigpickup" => "onbigpickuppickup", "held" => "onbigpickupheld", "holding" => "onbigpickupheld", "bigpickupheld" => "onbigpickupheld", "bigpickupholding" => "onbigpickupheld", "onbigpickupheld" => "onbigpickupheld", "onbigpickupholding" => "onbigpickupheld", "onbigpickuppickuprepeat" => "onbigpickupheld", "onbigpickuppickupinside" => "onbigpickupheld", "drop" => "onbigpickupdrop", "dropped" => "onbigpickupdrop", "ondrop" => "onbigpickupdrop", "onbigpickupdrop" => "onbigpickupdrop", "onbigpickupdropped" => "onbigpickupdrop", "placed" => "onbigpickupplaced", "inlevel" => "onbigpickupplaced", "bigpickupplaced" => "onbigpickupplaced", "bigpickupinlevel" => "onbigpickupplaced", "onbigpickupplaced" => "onbigpickupplaced", "onbigpickupinlevel" => "onbigpickupplaced", "onbigpickupdroprepeat" =>