Decompiled source of AlarmSpawnTeller v0.5.0
plugins/AlarmSpawnTeller/AlarmSpawnTeller.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.Globalization; 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 System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using Enemies; using HarmonyLib; using Il2CppInterop.Runtime.Injection; using Localization; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("AlarmSpawnTeller")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.5.0")] [assembly: AssemblyInformationalVersion("0.5.0")] [assembly: AssemblyProduct("AlarmSpawnTeller")] [assembly: AssemblyTitle("AlarmSpawnTeller")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.5.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.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace AlarmSpawnTeller { internal enum SpawnSource { UnknownDynamic, DoorAlarm, ErrorAlarm, ScoutScream, SurvivalWave, BloodDoor } internal enum SpawnSourceKind { None, Dynamic, BloodDoor } [BepInPlugin("logic.gtfo.alarmspawnteller", "Alarm Spawn Teller", "0.5.0")] public class Plugin : BasePlugin { private const string PluginGuid = "logic.gtfo.alarmspawnteller"; private const string PluginName = "Alarm Spawn Teller"; private const string PluginVersion = "0.5.0"; internal static ManualLogSource LogSource; public override void Load() { //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Expected O, but got Unknown LogSource = ((BasePlugin)this).Log; ((BasePlugin)this).Log.LogInfo((object)"[Alarm Spawn Teller] Installing patches..."); ConfigEntry<bool> debugSourceMode = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "DebugSourceMode", false, "Enables Blood Door spawn source investigation logs. This does not enable Blood Door classification."); ConfigEntry<bool> enableBloodDoorCandidateClassification = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "EnableBloodDoorCandidateClassification", false, "Enables the experimental Blood Door candidate rule for tracker routing."); ConfigEntry<string> bloodDoorSettingsNameKeywords = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "BloodDoorSettingsNameKeywords", "MainframeRush_ModificationWave,Blood,Door", "Comma-separated settings.name keywords used by the experimental Blood Door candidate rule."); ConfigEntry<bool> enableSpawnDataBloodDoorCandidateClassification = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "EnableSpawnDataBloodDoorCandidateClassification", true, "Enables experimental pEnemyGroupSpawnData groupType routing to the Blood Door tracker."); ConfigEntry<string> bloodDoorSpawnDataGroupTypes = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "BloodDoorSpawnDataGroupTypes", "Hunters", "Comma-separated pEnemyGroupSpawnData groupType values used by the experimental Blood Door candidate rule."); ConfigEntry<string> bloodDoorSpawnDataSpawnTypes = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "BloodDoorSpawnDataSpawnTypes", "OnLine,RandomInArea", "Comma-separated pEnemyGroupSpawnData spawnType values allowed by the experimental Blood Door candidate rule."); ConfigEntry<string> excludedBloodDoorSpawnDataSpawnTypes = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "ExcludedBloodDoorSpawnDataSpawnTypes", "Birther", "Comma-separated pEnemyGroupSpawnData spawnType values excluded from the experimental Blood Door candidate rule."); ConfigEntry<bool> enableEnemySetupProbe = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "EnableEnemySetupProbe", false, "Logs matching EnemyAgent.Setup decisions without changing spawn tracking."); ConfigEntry<string> enemySetupProbeNameKeywords = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "EnemySetupProbeNameKeywords", "Tank,BossTank,Tank_Boss,Pouncer", "Comma-separated enemy name keywords used by the EnemyAgent.Setup diagnostic probe."); ConfigEntry<bool> enableSpecialEnemyFallbackTracking = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "EnableSpecialEnemyFallbackTracking", true, "Enables experimental Dynamic tracker fallback for configured special enemies."); ConfigEntry<string> specialEnemyFallbackNameKeywords = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "SpecialEnemyFallbackNameKeywords", "Tank,BossTank,Tank_Boss,Pouncer", "Comma-separated enemy name keywords eligible for special enemy fallback tracking."); ConfigEntry<string> specialEnemyFallbackTarget = ((BasePlugin)this).Config.Bind<string>("Diagnostics", "SpecialEnemyFallbackTarget", "Dynamic", "Target tracker for special enemy fallback. Only Dynamic is currently supported."); ConfigEntry<bool> specialEnemyFallbackRequireAgressiveMode = ((BasePlugin)this).Config.Bind<bool>("Diagnostics", "SpecialEnemyFallbackRequireAgressiveMode", true, "Requires spawnData mode to be Agressive or Aggressive for special enemy fallback tracking."); ConfigEntry<bool> dynamicAutoHideEnabled = ((BasePlugin)this).Config.Bind<bool>("HUD", "DynamicAutoHideEnabled", true, "Removes Dynamic Spawns enemy-type rows after that type remains at zero alive."); ConfigEntry<float> dynamicAutoHideAfterSeconds = ((BasePlugin)this).Config.Bind<float>("HUD", "DynamicAutoHideAfterSeconds", 5f, "Seconds a Dynamic enemy type must remain at zero alive before that type row is removed. Minimum 1 second."); EnemyGroupSpawnPatch.ConfigureSourceInvestigation(debugSourceMode, enableBloodDoorCandidateClassification, bloodDoorSettingsNameKeywords, enableSpawnDataBloodDoorCandidateClassification, bloodDoorSpawnDataGroupTypes, bloodDoorSpawnDataSpawnTypes, excludedBloodDoorSpawnDataSpawnTypes, enableEnemySetupProbe, enemySetupProbeNameKeywords, enableSpecialEnemyFallbackTracking, specialEnemyFallbackNameKeywords, specialEnemyFallbackTarget, specialEnemyFallbackRequireAgressiveMode); EnemyGroupSpawnPatch.ConfigureDynamicAutoHide(dynamicAutoHideEnabled, dynamicAutoHideAfterSeconds); Localizer.LoadLocalization(); InstallTicker(); EnemyGroupSpawnPatch.Apply(new Harmony("logic.gtfo.alarmspawnteller")); ((BasePlugin)this).Log.LogInfo((object)"Alarm Spawn Teller loaded successfully."); } private void InstallTicker() { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Expected O, but got Unknown //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown bool flag = default(bool); try { ClassInjector.RegisterTypeInIl2Cpp<AlarmSpawnTicker>(); } catch (Exception ex) { ManualLogSource log = ((BasePlugin)this).Log; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(70, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] AlarmSpawnTicker registration skipped or failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } log.LogWarning(val); } try { ((BasePlugin)this).AddComponent<AlarmSpawnTicker>(); ((BasePlugin)this).Log.LogInfo((object)"[Alarm Spawn Teller] AlarmSpawnTicker added."); } catch (Exception ex2) { ManualLogSource log2 = ((BasePlugin)this).Log; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(53, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[Alarm Spawn Teller] Failed to add AlarmSpawnTicker: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex2); } log2.LogError(val2); } } } public sealed class AlarmSpawnTicker : MonoBehaviour { private enum HudDisplayState { Idle, ActiveSession, FinalSummary } private const float LanguageRefreshIntervalSeconds = 3f; private const float HudBoxWidth = 320f; private const float HudRightMargin = 30f; private const float HudBoxGap = 8f; private static readonly List<float> PendingScanTimes = new List<float>(); private static bool HasLoggedUpdate; private static bool IsHudDisabled; private static string DynamicHudMessage; private static string BloodDoorHudMessage; private static float DynamicHudMessageEndTime; private static GUIStyle HudStyle; private static HudDisplayState HudState; private static float NextLanguageRefreshTime; public AlarmSpawnTicker(IntPtr pointer) : base(pointer) { } internal static void ScheduleScan(float delaySeconds) { PendingScanTimes.Add(Time.time + delaySeconds); Plugin.LogSource.LogInfo((object)"[Alarm Spawn Teller] Scheduled delayed scan."); } internal static bool HasPendingScans() { return PendingScanTimes.Count > 0; } internal static void ShowAlarmSpawnMessage(string message, float durationSeconds = 8f) { if (!IsHudDisabled) { DynamicHudMessage = message; DynamicHudMessageEndTime = Time.time + durationSeconds; HudState = HudDisplayState.FinalSummary; } } internal static void ShowActiveAlarmSpawnMessage(string message) { if (!IsHudDisabled) { DynamicHudMessage = message; HudState = HudDisplayState.ActiveSession; } } internal static void HideAlarmSpawnMessage() { DynamicHudMessage = null; DynamicHudMessageEndTime = 0f; HudState = HudDisplayState.Idle; } internal static void ShowBloodDoorSpawnMessage(string message) { if (!IsHudDisabled) { BloodDoorHudMessage = message; } } internal static void HideBloodDoorSpawnMessage() { BloodDoorHudMessage = null; } private void Update() { float time = Time.time; if (!HasLoggedUpdate) { HasLoggedUpdate = true; EnemyGroupSpawnPatch.MarkStartupTime(); if (EnemyGroupSpawnPatch.DebugModeEnabled) { Plugin.LogSource.LogInfo((object)"[Alarm Spawn Teller] AlarmSpawnTicker.Update is running."); } } if (Localizer.IsAutoLanguage && time >= NextLanguageRefreshTime) { NextLanguageRefreshTime = time + 3f; Localizer.RefreshAutoLanguage(); } EnemyGroupSpawnPatch.ProcessCaptureWindowTimeout(); EnemyGroupSpawnPatch.ProcessPendingAgentRecordResolves(); EnemyGroupSpawnPatch.ProcessTrackedAlarmEnemyAliveStates(); EnemyGroupSpawnPatch.ProcessDynamicAutoHide(); EnemyGroupSpawnPatch.ProcessAlarmSessionTimeout(); if (PendingScanTimes.Count == 0) { return; } for (int num = PendingScanTimes.Count - 1; num >= 0; num--) { if (!(time < PendingScanTimes[num])) { PendingScanTimes.RemoveAt(num); if (EnemyGroupSpawnPatch.DebugModeEnabled) { Plugin.LogSource.LogInfo((object)"[Alarm Spawn Teller] Running delayed scan."); } EnemyGroupSpawnPatch.ScanForNewEnemies(); } } } private void OnGUI() { //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Expected O, but got Unknown if (IsHudDisabled) { return; } if (HudState == HudDisplayState.FinalSummary && Time.time >= DynamicHudMessageEndTime) { HudState = HudDisplayState.Idle; DynamicHudMessage = null; } try { if (HudStyle == null) { HudStyle = CreateHudStyle(); } List<string> list = new List<string>(2); if (HudState != HudDisplayState.Idle && HasHudStatisticsContent(DynamicHudMessage)) { list.Add(DynamicHudMessage); } if (HasHudStatisticsContent(BloodDoorHudMessage)) { list.Add(BloodDoorHudMessage); } if (list.Count != 0) { float[] array = list.Select(GetHudBoxHeight).ToArray(); float num = array.Sum() + 8f * (float)(list.Count - 1); float x = Mathf.Max(0f, (float)Screen.width - 320f - 30f); float num2 = Mathf.Max(0f, ((float)Screen.height - num) * 0.5f); for (int i = 0; i < list.Count; i++) { DrawHudBox(list[i], x, num2, array[i]); num2 += array[i] + 8f; } } } catch (Exception ex) { IsHudDisabled = true; ManualLogSource logSource = Plugin.LogSource; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(61, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] HUD overlay disabled after OnGUI error: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } logSource.LogWarning(val); } } private static float GetHudBoxHeight(string message) { int num = message.Count((char character) => character == '\n') + 1; return Math.Max(70f, 32f + (float)num * 24f); } private static bool HasHudStatisticsContent(string message) { if (string.IsNullOrWhiteSpace(message)) { return false; } return message.Split('\n', StringSplitOptions.RemoveEmptyEntries).Count((string line) => !string.IsNullOrWhiteSpace(line)) >= 2; } private static void DrawHudBox(string message, float x, float y, float height) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) GUI.Box(new Rect(x, y, 320f, height), message, HudStyle); } private static GUIStyle CreateHudStyle() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown GUIStyle val = new GUIStyle(GUI.skin.box) { alignment = (TextAnchor)0, fontSize = 20, wordWrap = true, padding = new RectOffset(14, 14, 10, 10) }; val.normal.textColor = Color.white; return val; } } internal static class Localizer { private sealed class LocalizationConfig { public string Language { get; set; } public Dictionary<string, Dictionary<string, string>> HudTexts { get; set; } public Dictionary<string, EnemyNameEntry> EnemyNames { get; set; } } private sealed class EnemyNameEntry { public string EnemyDataBlockID { get; set; } public string EnemyDataName { get; set; } public string[] Aliases { get; set; } public string Chinese { get; set; } public string English { get; set; } public string Japanese { get; set; } } private const string AutoLanguage = "Auto"; private const string EnglishFallbackLanguage = "English"; private const string LocalizationFileName = "AlarmSpawnTeller.localization.json"; private static LocalizationConfig Config; private static bool HasLoggedAutoLanguageWarning; internal static string ConfiguredLanguage { get; private set; } = "Auto"; internal static string ResolvedLanguage { get; private set; } = "English"; internal static bool IsAutoLanguage { get; private set; } = true; internal static void LoadLocalization() { //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected O, but got Unknown bool flag = default(bool); try { string text = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? AppContext.BaseDirectory, "AlarmSpawnTeller.localization.json"); if (!File.Exists(text)) { ManualLogSource logSource = Plugin.LogSource; if (logSource != null) { BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(94, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Localization JSON not found beside plugin DLL: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; using built-in fallback."); } logSource.LogWarning(val); } Config = CreateMinimalFallbackConfig(); ResolveConfiguredLanguage(); } else { Config = JsonSerializer.Deserialize<LocalizationConfig>(File.ReadAllText(text)) ?? CreateMinimalFallbackConfig(); EnsureDefaults(Config); ResolveConfiguredLanguage(); } } catch (Exception ex) { ManualLogSource logSource = Plugin.LogSource; if (logSource != null) { BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(80, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Failed to load localization JSON; using built-in fallback. "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ex.Message); } logSource.LogWarning(val); } Config = CreateMinimalFallbackConfig(); ResolveConfiguredLanguage(); } } internal unsafe static void RefreshAutoLanguage() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (!IsAutoLanguage) { return; } try { ITextLocalizationService textLocalizationService = Text.TextLocalizationService; if (textLocalizationService == null) { SetAutoLanguageFallback("GTFO text localization service is not initialized."); return; } Language currentLanguage = textLocalizationService.CurrentLanguage; string text = ((object)(*(Language*)(¤tLanguage))/*cast due to .constrained prefix*/).ToString(); if (string.IsNullOrWhiteSpace(text)) { SetAutoLanguageFallback("GTFO current language was empty."); return; } string text2 = MapGameLanguage(currentLanguage) ?? NormalizeLanguage(text); if (text2 == null) { SetAutoLanguageFallback("GTFO language '" + text + "' is not supported."); return; } UpdateResolvedLanguage(text2); HasLoggedAutoLanguageWarning = false; } catch (Exception ex) { SetAutoLanguageFallback("Failed to read GTFO current language: " + ex.Message); } } internal static string GetText(string key, params object[] args) { string text = ResolveLocalizedValue(Config?.HudTexts, key, key); if (args == null || args.Length == 0) { return text; } try { return string.Format(text, args); } catch { return text; } } internal static string GetEnemyDisplayName(string rawEnemyDataName) { return GetEnemyDisplayName(null, rawEnemyDataName); } internal static string GetEnemyDisplayName(string enemyDataID, string rawEnemyDataName) { string text = StripEnemyIdPrefix(rawEnemyDataName); EnemyNameEntry enemyNameEntry = FindEnemyNameByRawName(text); if (enemyNameEntry != null) { return ResolveEnemyName(enemyNameEntry, text); } string text2 = NormalizeEnemyDataID(enemyDataID); if (!string.IsNullOrWhiteSpace(text2) && Config?.EnemyNames != null) { foreach (KeyValuePair<string, EnemyNameEntry> enemyName in Config.EnemyNames) { if (string.Equals(enemyName.Value?.EnemyDataBlockID, text2, StringComparison.OrdinalIgnoreCase)) { return ResolveEnemyName(enemyName.Value, text); } } } return CleanEnemyName(text); } private static string NormalizeEnemyDataID(string enemyDataID) { if (string.IsNullOrWhiteSpace(enemyDataID)) { return null; } string text = enemyDataID.Trim(); if (!uint.TryParse(text, out var result)) { return text; } return result.ToString(); } private static EnemyNameEntry FindEnemyNameByRawName(string rawName) { if (Config?.EnemyNames == null || string.IsNullOrWhiteSpace(rawName)) { return null; } if (Config.EnemyNames.TryGetValue(rawName, out var value)) { return value; } foreach (KeyValuePair<string, EnemyNameEntry> enemyName in Config.EnemyNames) { EnemyNameEntry value2 = enemyName.Value; if (value2 != null) { if (string.Equals(value2.EnemyDataName, rawName, StringComparison.OrdinalIgnoreCase)) { return value2; } if (value2.Aliases != null && value2.Aliases.Any((string alias) => !string.IsNullOrWhiteSpace(alias) && string.Equals(alias, rawName, StringComparison.OrdinalIgnoreCase))) { return value2; } } } return null; } private static string ResolveEnemyName(EnemyNameEntry entry, string fallbackRawName) { if (entry == null) { return CleanEnemyName(fallbackRawName); } string resolvedLanguage = ResolvedLanguage; string text = ((resolvedLanguage == "Chinese") ? entry.Chinese : ((!(resolvedLanguage == "Japanese")) ? entry.English : entry.Japanese)); string text2 = text; if (!string.IsNullOrWhiteSpace(text2)) { return text2; } if (!string.IsNullOrWhiteSpace(entry.English)) { return entry.English; } return CleanEnemyName(entry.EnemyDataName ?? fallbackRawName); } internal static string GetSourceDisplayName(SpawnSource source) { return GetText($"Source_{source}"); } private static string ResolveLocalizedValue(Dictionary<string, Dictionary<string, string>> table, string key, string fallback) { if (table == null || string.IsNullOrWhiteSpace(key) || !table.TryGetValue(key, out var value) || value == null) { return fallback; } if (value.TryGetValue(ResolvedLanguage, out var value2) && !string.IsNullOrWhiteSpace(value2)) { return value2; } if (value.TryGetValue("English", out value2) && !string.IsNullOrWhiteSpace(value2)) { return value2; } return fallback; } private static string StripEnemyIdPrefix(string rawEnemyDataName) { string text = rawEnemyDataName ?? string.Empty; int num = text.IndexOf(" / ", StringComparison.Ordinal); if (num >= 0 && num + 3 < text.Length) { text = text.Substring(num + 3); } return text; } private static string CleanEnemyName(string rawEnemyDataName) { string text = StripEnemyIdPrefix(rawEnemyDataName); return text switch { "Striker_Big_Wave" => "Big Striker", "Shooter_Wave" => "Shooter", "Striker_Wave" => "Striker", _ => text.Replace("_Wave", string.Empty), }; } private static void EnsureDefaults(LocalizationConfig config) { LocalizationConfig localizationConfig = CreateMinimalFallbackConfig(); config.Language = (string.IsNullOrWhiteSpace(config.Language) ? localizationConfig.Language : config.Language); LocalizationConfig localizationConfig2 = config; if (localizationConfig2.HudTexts == null) { Dictionary<string, Dictionary<string, string>> dictionary = (localizationConfig2.HudTexts = new Dictionary<string, Dictionary<string, string>>()); } localizationConfig2 = config; if (localizationConfig2.EnemyNames == null) { Dictionary<string, EnemyNameEntry> dictionary3 = (localizationConfig2.EnemyNames = new Dictionary<string, EnemyNameEntry>()); } MergeMissing(config.HudTexts, localizationConfig.HudTexts); MergeMissingEnemyNames(config.EnemyNames, localizationConfig.EnemyNames); } private static void MergeMissing(Dictionary<string, Dictionary<string, string>> target, Dictionary<string, Dictionary<string, string>> defaults) { foreach (KeyValuePair<string, Dictionary<string, string>> @default in defaults) { if (!target.TryGetValue(@default.Key, out var value) || value == null) { target[@default.Key] = new Dictionary<string, string>(@default.Value); continue; } foreach (KeyValuePair<string, string> item in @default.Value) { value.TryAdd(item.Key, item.Value); } } } private static void MergeMissingEnemyNames(Dictionary<string, EnemyNameEntry> target, Dictionary<string, EnemyNameEntry> defaults) { foreach (KeyValuePair<string, EnemyNameEntry> @default in defaults) { target.TryAdd(@default.Key, @default.Value); } } private static string SerializeConfig(LocalizationConfig config) { return JsonSerializer.Serialize(config, new JsonSerializerOptions { WriteIndented = true }); } private static LocalizationConfig CreateMinimalFallbackConfig() { return new LocalizationConfig { Language = "Auto", HudTexts = new Dictionary<string, Dictionary<string, string>> { ["Source_SurvivalWave"] = Text("动态刷怪", "Dynamic spawns", "動的スポーン"), ["Source_UnknownDynamic"] = Text("其他动态刷怪", "Other dynamic spawns", "その他の動的スポーン"), ["Source_DoorAlarm"] = Text("开门警报", "Door alarm", "ドアアラーム"), ["Source_ErrorAlarm"] = Text("无限警报", "Error alarm", "エラーアラーム"), ["Source_ScoutScream"] = Text("哨兵叫怪", "Scout scream", "スカウト叫び"), ["SourceSummaryLine"] = Text("{0}存活:{1} / 已刷新:{2}", "{0} alive: {1} / spawned: {2}", "{0}生存:{1} / 出現:{2}"), ["EnemyCountLine"] = Text("{0}:{1} / {2}只", "{0}: {1} / {2}", "{0}:{1} / {2}体"), ["BloodDoorSpawns"] = Text("血门刷怪", "Blood Door Spawns", "Blood Door Spawns"), ["BloodDoorAliveLine"] = Text("存活:{0} / 已生成:{1}", "Alive: {0} / Spawned: {1}", "Alive: {0} / Spawned: {1}") }, EnemyNames = new Dictionary<string, EnemyNameEntry> { ["Shooter_Wave"] = EnemyName("11", "Shooter_Wave", Array.Empty<string>(), "射手", "Shooter", "Shooter"), ["Striker_Wave"] = EnemyName("13", "Striker_Wave", Array.Empty<string>(), "近战怪", "Striker", "Striker"), ["Striker_Big_Wave"] = EnemyName("16", "Striker_Big_Wave", Array.Empty<string>(), "大近战", "Big Striker", "Big Striker") } }; } private static void ResolveConfiguredLanguage() { ConfiguredLanguage = (string.IsNullOrWhiteSpace(Config?.Language) ? "Auto" : Config.Language.Trim()); IsAutoLanguage = string.Equals(ConfiguredLanguage, "Auto", StringComparison.OrdinalIgnoreCase); if (IsAutoLanguage) { ResolvedLanguage = "English"; RefreshAutoLanguage(); } else { ResolvedLanguage = NormalizeLanguage(ConfiguredLanguage) ?? "English"; } } private static string NormalizeLanguage(string language) { if (string.IsNullOrWhiteSpace(language)) { return null; } switch (language.Trim().Replace("_", string.Empty).Replace("-", string.Empty) .Replace(" ", string.Empty) .ToLowerInvariant()) { case "english": case "en": case "enus": return "English"; case "chinese": case "zh": case "zhtw": case "zhcn": case "chinesesimplified": case "simplifiedchinese": case "chinesetraditional": case "traditionalchinese": return "Chinese"; case "ja": case "jajp": case "japanese": return "Japanese"; default: return null; } } private static string MapGameLanguage(Language language) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Expected I4, but got Unknown if ((int)language != 1) { return (language - 9) switch { 3 => "Chinese", 2 => "Chinese", 0 => "Japanese", _ => null, }; } return "English"; } private static void SetAutoLanguageFallback(string reason) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown UpdateResolvedLanguage("English"); if (HasLoggedAutoLanguageWarning) { return; } HasLoggedAutoLanguageWarning = true; ManualLogSource logSource = Plugin.LogSource; if (logSource != null) { bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(46, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(reason); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" Falling back to English."); } logSource.LogWarning(val); } } private static void UpdateResolvedLanguage(string language) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown if (string.Equals(ResolvedLanguage, language, StringComparison.Ordinal)) { return; } ResolvedLanguage = language; ManualLogSource logSource = Plugin.LogSource; if (logSource != null) { bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(56, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Localization language resolved to "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(ResolvedLanguage); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("."); } logSource.LogInfo(val); } } private static EnemyNameEntry EnemyName(string enemyDataBlockID, string enemyDataName, string[] aliases, string chinese, string english, string japanese) { return new EnemyNameEntry { EnemyDataBlockID = enemyDataBlockID, EnemyDataName = enemyDataName, Aliases = (aliases ?? Array.Empty<string>()), Chinese = chinese, English = english, Japanese = japanese }; } private static Dictionary<string, string> Text(string chinese, string english, string japanese) { return new Dictionary<string, string> { ["Chinese"] = chinese, ["English"] = english, ["Japanese"] = japanese }; } } internal static class EnemyGroupSpawnPatch { private sealed class SpawnCaptureRecord { internal string GroupType { get; private init; } internal string SpawnType { get; private init; } internal string PersistentGameDataID { get; private init; } internal string PersistentWavePopDataID { get; private init; } internal string SurvivalWaveReplicatorKey { get; private init; } internal string PopulationScore { get; private init; } internal string DebugEnemyMode { get; private init; } internal bool IsLikelyHibernating { get { if (!string.Equals(GroupType, "Hibernating", StringComparison.OrdinalIgnoreCase)) { return string.Equals(DebugEnemyMode, "Hibernate", StringComparison.OrdinalIgnoreCase); } return true; } } internal static SpawnCaptureRecord FromSpawnData(object spawnData) { return new SpawnCaptureRecord { GroupType = ReadSpawnDataValue(spawnData, "groupType"), SpawnType = ReadSpawnDataValue(spawnData, "spawnType"), PersistentGameDataID = ReadSpawnDataValue(spawnData, "persistentGameDataID"), PersistentWavePopDataID = ReadSpawnDataValue(spawnData, "persistentWavePopDataID"), SurvivalWaveReplicatorKey = ReadSpawnDataValue(spawnData, "survivalWaveReplicatorKey"), PopulationScore = ReadSpawnDataValue(spawnData, "populationScore"), DebugEnemyMode = ReadSpawnDataValue(spawnData, "debugEnemyMode") }; } internal string ToSummary() { return $"groupType={GroupType}; spawnType={SpawnType}; persistentGameDataID={PersistentGameDataID}; persistentWavePopDataID={PersistentWavePopDataID}; survivalWaveReplicatorKey={SurvivalWaveReplicatorKey}; populationScore={PopulationScore}; debugEnemyMode={DebugEnemyMode}"; } private static string ReadSpawnDataValue(object spawnData, string memberName) { object obj = TryGetMemberValue(spawnData, memberName) ?? TryGetMemberValue(spawnData, "m_" + memberName); if (obj != null) { return FormatValueSummary(obj); } return "<null>"; } } private sealed class AgentCaptureRecord { internal readonly object Instance; internal string AgentKey { get; private set; } internal string GroupKey { get; private set; } internal string EnemyData { get; private set; } internal string EnemyDataID { get; private set; } internal string EnemyDataName { get; private set; } internal string EnemyDataInternalName { get; private set; } internal string EnemyDataPersistentID { get; private set; } internal string InstanceName { get; private set; } internal string PersistentGameDataID { get; private set; } internal string PersistentID { get; private set; } internal string PopulationID { get; private set; } internal string EnemyID { get; private set; } internal string Type { get; private set; } internal string Name { get; private set; } internal bool NeedsDelayedResolve { get; private set; } internal float ResolveAfterTime { get; private set; } private AgentCaptureRecord(object instance, string agentKey) { Instance = instance; AgentKey = agentKey; } internal static AgentCaptureRecord FromSetup(object instance, object spawnData, string agentKey) { AgentCaptureRecord agentCaptureRecord = new AgentCaptureRecord(instance, agentKey); agentCaptureRecord.ReadSetupSpawnDataFallback(spawnData); agentCaptureRecord.ResolveFromInstance(); return agentCaptureRecord; } internal string ToSummary() { return $"agentKey={AgentKey}; groupKey={GroupKey}; enemyDataID={EnemyDataID}; enemyDataName={EnemyDataName}; enemyDataInternalName={EnemyDataInternalName}; enemyDataPersistentID={EnemyDataPersistentID}; instanceName={InstanceName}; spawnDataEnemyData={EnemyData}; persistentGameDataID={PersistentGameDataID}; persistentID={PersistentID}; populationID={PopulationID}; enemyID={EnemyID}; type={Type}; name={Name}"; } internal void ResolveFromInstance() { EnemyDataID = FirstKnown(ReadInstanceValue("EnemyDataID"), ReadInstanceValue("enemyDataID"), EnemyDataID); object obj = TryGetMemberValue(Instance, "EnemyData") ?? TryGetMemberValue(Instance, "enemyData") ?? TryGetMemberValue(Instance, "m_enemyData"); if (obj != null) { EnemyData = FirstKnown(FormatValueSummary(obj), EnemyData); EnemyDataName = FirstKnown(ReadObjectValue(obj, "name"), EnemyDataName); EnemyDataInternalName = FirstKnown(ReadObjectValue(obj, "internalName"), EnemyDataInternalName); EnemyDataPersistentID = FirstKnown(ReadObjectValue(obj, "persistentID"), EnemyDataPersistentID); } InstanceName = FirstKnown(TryGetUnityObjectName(Instance), ReadInstanceValue("name"), InstanceName); UpdateGroupKey(); NeedsDelayedResolve = IsUnknown(EnemyDataID) && IsUnknown(EnemyDataPersistentID) && IsUnknown(EnemyDataName) && IsUnknown(EnemyDataInternalName); if (NeedsDelayedResolve && ResolveAfterTime <= 0f) { ResolveAfterTime = Time.time + 0.2f; } } private void ReadSetupSpawnDataFallback(object spawnData) { EnemyData = ReadSetupDataValue(spawnData, "enemyData"); EnemyDataID = ReadSetupDataValue(spawnData, "enemyDataID"); PersistentGameDataID = ReadSetupDataValue(spawnData, "persistentGameDataID"); PersistentID = ReadSetupDataValue(spawnData, "persistentID"); PopulationID = ReadSetupDataValue(spawnData, "populationID"); EnemyID = ReadSetupDataValue(spawnData, "enemyID"); Type = ReadSetupDataValue(spawnData, "type"); Name = ReadSetupDataValue(spawnData, "name"); UpdateGroupKey(); } private void UpdateGroupKey() { string text = FirstKnown(EnemyDataID, EnemyDataPersistentID, PersistentGameDataID, PersistentID, EnemyID); string text2 = FirstKnown(EnemyDataName, EnemyDataInternalName, InstanceName, Name, Type); if (!IsUnknown(text) && !IsUnknown(text2)) { GroupKey = text + " / " + text2; return; } GroupKey = FirstKnown(text, text2, EnemyData, AgentKey, "<unknown enemy>"); } private string ReadInstanceValue(string memberName) { return ReadObjectValue(Instance, memberName); } private static string ReadObjectValue(object source, string memberName) { object obj = TryGetMemberValue(source, memberName) ?? TryGetMemberValue(source, "m_" + memberName); if (obj != null) { return FormatValueSummary(obj); } return "<null>"; } private static string ReadSetupDataValue(object spawnData, string memberName) { if (spawnData == null) { return "<null>"; } object obj = TryGetMemberValue(spawnData, memberName) ?? TryGetMemberValue(spawnData, "m_" + memberName); if (obj != null) { return FormatValueSummary(obj); } return "<null>"; } internal static string FirstKnown(params string[] values) { foreach (string text in values) { if (!string.IsNullOrWhiteSpace(text) && text != "<null>") { return text; } } return "<unknown enemy>"; } private static bool IsUnknown(string value) { if (!string.IsNullOrWhiteSpace(value) && !(value == "<null>")) { return value == "<unknown enemy>"; } return true; } } private sealed class SourceDumpMember { internal string Name { get; } internal object Value { get; } internal SourceDumpMember(string name, object value) { Name = name; Value = value; } } private sealed class BloodDoorCandidateEvaluation { internal bool SurvivalWavePresent { get; set; } internal bool SettingsPresent { get; set; } internal string SettingsName { get; set; } = "<null>"; internal string SettingsPersistentID { get; set; } = "<null>"; internal string PopulationDataID { get; set; } = "<null>"; internal string SpawnType { get; set; } = "<null>"; internal string OverrideWaveSpawnType { get; set; } = "<null>"; internal string WavePauseMin { get; set; } = "<null>"; internal string WavePauseMax { get; set; } = "<null>"; internal bool OverrideWaveSpawnTypeMatched { get; set; } internal bool SpawnTypeMatched { get; set; } internal bool WavePauseMinMatched { get; set; } internal bool WavePauseMaxMatched { get; set; } internal bool KeywordMatched { get; set; } internal bool CandidateMatched { get; set; } } private sealed class SpawnDataBloodDoorCandidateEvaluation { internal string GroupType { get; set; } = "<null>"; internal string DebugEnemyMode { get; set; } = "<null>"; internal string SpawnType { get; set; } = "<null>"; internal string PersistentGameDataID { get; set; } = "<null>"; internal string PersistentWavePopDataID { get; set; } = "<null>"; internal string SurvivalWaveReplicatorKey { get; set; } = "<null>"; internal string CourseNodeIDPlusOne { get; set; } = "<null>"; internal string AllowedSpawnTypes { get; set; } = "<empty>"; internal string ExcludedSpawnTypes { get; set; } = "<empty>"; internal bool GroupTypeMatched { get; set; } internal bool SpawnTypeMatched { get; set; } internal bool SpawnTypeExcluded { get; set; } internal bool CandidateMatched { get; set; } } private sealed class SpawnDataRouteEvaluation { internal SpawnDataBloodDoorCandidateEvaluation Candidate { get; init; } internal SpawnSourceKind CandidateSourceKind { get; init; } internal SpawnSourceKind InheritedSessionSourceKind { get; init; } internal SpawnSourceKind FinalSourceKind { get; init; } internal bool ForcedNotBloodDoor { get; init; } internal bool RouteExclusive { get; init; } internal bool SkippedDynamicBecauseBloodDoorCandidate { get; init; } internal string CandidateReason { get; init; } internal string TargetTracker { get; init; } } private sealed class SpawnPatchState { internal SpawnDataRouteEvaluation SpawnDataRoute { get; set; } } private sealed class SpecialEnemyFallbackEvaluation { internal string SpawnDataMode { get; init; } internal string GroupReplicatorKey { get; init; } internal bool KeywordMatched { get; init; } internal bool AggressiveModeMatched { get; init; } internal bool FallbackEnabled { get; init; } internal bool FallbackMatched { get; init; } internal string TargetTracker { get; init; } internal string Decision { get; init; } internal bool ShouldLog { get; init; } } private sealed class SpawnTracker { private readonly Dictionary<string, TrackedAlarmEnemy> TrackedEnemies = new Dictionary<string, TrackedAlarmEnemy>(); private readonly Dictionary<string, SpawnTypeEntry> TrackedTypeEntries = new Dictionary<string, SpawnTypeEntry>(StringComparer.Ordinal); internal string Name { get; } internal int TotalSpawned => TrackedEnemies.Count; internal int AliveCount => TrackedEnemies.Values.Count((TrackedAlarmEnemy enemy) => enemy.IsAlive); internal IEnumerable<TrackedAlarmEnemy> Enemies => TrackedEnemies.Values; internal IEnumerable<SpawnTypeEntry> TypeEntries => TrackedTypeEntries.Values; internal int TypeEntryCount => TrackedTypeEntries.Count; internal SpawnTracker(string name) { Name = name; } internal bool TrackEnemy(AgentCaptureRecord record, SpawnSource spawnSource) { if (record == null || string.IsNullOrWhiteSpace(record.AgentKey) || TrackedEnemies.ContainsKey(record.AgentKey)) { return false; } TrackedAlarmEnemy trackedAlarmEnemy = TrackedAlarmEnemy.FromCaptureRecord(record, spawnSource); TrackedEnemies[record.AgentKey] = trackedAlarmEnemy; AddTrackedEnemyToTypeEntry(trackedAlarmEnemy, Time.time); return true; } internal bool TryMarkDead(string uniqueKey) { if (string.IsNullOrWhiteSpace(uniqueKey) || !TrackedEnemies.TryGetValue(uniqueKey, out var value) || !value.IsAlive) { return false; } value.IsAlive = false; if (TrackedTypeEntries.TryGetValue(value.TypeKey, out var value2)) { int alive = value2.Alive; value2.Alive = Math.Max(0, value2.Alive - 1); if (alive > 0 && value2.Alive == 0 && !value2.ZeroAliveTimeValid) { value2.ZeroAliveTime = Time.time; value2.ZeroAliveTimeValid = true; } } return true; } internal bool TryMarkAlive(string uniqueKey) { if (string.IsNullOrWhiteSpace(uniqueKey) || !TrackedEnemies.TryGetValue(uniqueKey, out var value) || value.IsAlive) { return false; } value.IsAlive = true; if (TrackedTypeEntries.TryGetValue(value.TypeKey, out var value2)) { value2.Alive++; value2.ZeroAliveTime = 0f; value2.ZeroAliveTimeValid = false; } return true; } internal void UpdateEnemy(AgentCaptureRecord record) { if (record != null && TrackedEnemies.TryGetValue(record.AgentKey, out var value)) { string typeKey = value.TypeKey; bool isAlive = value.IsAlive; value.UpdateFromCaptureRecord(record); SpawnTypeEntry value2; if (!string.Equals(typeKey, value.TypeKey, StringComparison.Ordinal)) { RemoveTrackedEnemyFromTypeEntry(typeKey, isAlive); AddTrackedEnemyToTypeEntry(value, Time.time); } else if (TrackedTypeEntries.TryGetValue(value.TypeKey, out value2)) { value2.DisplayName = value.DisplayName; } } } internal IEnumerable<SpawnTypeEntry> GetPrunableTypeEntries(float now, float timeoutSeconds) { return TrackedTypeEntries.Values.Where((SpawnTypeEntry entry) => entry.Total > 0 && entry.Alive == 0 && entry.ZeroAliveTimeValid && now - entry.ZeroAliveTime >= timeoutSeconds); } internal string[] PruneTypeEntry(string typeKey) { if (string.IsNullOrWhiteSpace(typeKey) || !TrackedTypeEntries.TryGetValue(typeKey, out var value) || value.Total <= 0 || value.Alive != 0 || !value.ZeroAliveTimeValid) { return Array.Empty<string>(); } string[] array = (from enemy in TrackedEnemies.Values where !enemy.IsAlive && string.Equals(enemy.TypeKey, typeKey, StringComparison.Ordinal) select enemy.UniqueKey).ToArray(); string[] array2 = array; foreach (string key in array2) { TrackedEnemies.Remove(key); } TrackedTypeEntries.Remove(typeKey); return array; } internal void Clear() { TrackedEnemies.Clear(); TrackedTypeEntries.Clear(); } private void AddTrackedEnemyToTypeEntry(TrackedAlarmEnemy tracked, float now) { if (!TrackedTypeEntries.TryGetValue(tracked.TypeKey, out var value)) { value = new SpawnTypeEntry { TypeKey = tracked.TypeKey, DisplayName = tracked.DisplayName }; TrackedTypeEntries[tracked.TypeKey] = value; } value.DisplayName = tracked.DisplayName; value.Total++; if (tracked.IsAlive) { value.Alive++; } value.LastAddedTime = now; value.ZeroAliveTime = 0f; value.ZeroAliveTimeValid = false; } private void RemoveTrackedEnemyFromTypeEntry(string typeKey, bool wasAlive) { if (TrackedTypeEntries.TryGetValue(typeKey, out var value)) { value.Total = Math.Max(0, value.Total - 1); if (wasAlive) { value.Alive = Math.Max(0, value.Alive - 1); } if (value.Total == 0) { TrackedTypeEntries.Remove(typeKey); } else if (value.Alive == 0 && !value.ZeroAliveTimeValid) { value.ZeroAliveTime = Time.time; value.ZeroAliveTimeValid = true; } } } } private sealed class SpawnTypeEntry { internal string TypeKey { get; set; } internal string DisplayName { get; set; } internal int Total { get; set; } internal int Alive { get; set; } internal float LastAddedTime { get; set; } internal float ZeroAliveTime { get; set; } internal bool ZeroAliveTimeValid { get; set; } } private sealed class TrackedAlarmEnemy { internal string UniqueKey { get; private set; } internal object Agent { get; private set; } internal string EnemyDataID { get; private set; } internal string EnemyDataName { get; private set; } internal string DisplayName { get; private set; } internal string TypeKey { get; private set; } internal SpawnSource SpawnSource { get; private set; } internal bool IsAlive { get; set; } internal static TrackedAlarmEnemy FromCaptureRecord(AgentCaptureRecord record, SpawnSource spawnSource) { TrackedAlarmEnemy trackedAlarmEnemy = new TrackedAlarmEnemy(); trackedAlarmEnemy.UniqueKey = record.AgentKey; trackedAlarmEnemy.Agent = record.Instance; trackedAlarmEnemy.SpawnSource = spawnSource; trackedAlarmEnemy.IsAlive = true; trackedAlarmEnemy.UpdateFromCaptureRecord(record); return trackedAlarmEnemy; } internal void UpdateFromCaptureRecord(AgentCaptureRecord record) { if (record != null) { Agent = record.Instance ?? Agent; EnemyDataID = AgentCaptureRecord.FirstKnown(record.EnemyDataID, record.EnemyDataPersistentID, record.PersistentGameDataID, EnemyDataID); EnemyDataName = AgentCaptureRecord.FirstKnown(record.EnemyDataName, record.EnemyDataInternalName, record.InstanceName, record.Name, record.Type, EnemyDataName); TypeKey = BuildStableEnemyTypeKey(EnemyDataID, EnemyDataName, record.GroupKey, UniqueKey); DisplayName = FormatEnemyNameForHud(EnemyDataID, AgentCaptureRecord.FirstKnown(EnemyDataName, record.GroupKey, UniqueKey, "<unknown enemy>")); } } private static string BuildStableEnemyTypeKey(string enemyDataID, string enemyDataName, string groupKey, string uniqueKey) { string text = AgentCaptureRecord.FirstKnown(enemyDataID); string text2 = AgentCaptureRecord.FirstKnown(enemyDataName); if (text != "<unknown enemy>" || text2 != "<unknown enemy>") { return text + "|" + text2; } return AgentCaptureRecord.FirstKnown(groupKey, uniqueKey, "<unknown enemy type>"); } } private static readonly bool DebugMode = false; private const int MaxSourceDumpMembers = 80; private const int MaxSpawnDataDumpLines = 80; private const int MaxNestedSpawnDataDumpMembers = 30; private static readonly string[] SourceTypeDiscoveryKeywords = new string[11] { "Blood", "Door", "Warden", "Alarm", "SurvivalWave", "Wave", "Population", "EnemyGroup", "Spawn", "Security", "ChainedPuzzle" }; private static readonly string[] SurvivalWaveDumpKeywords = new string[16] { "Wave", "Type", "Population", "Settings", "Data", "Persistent", "ID", "Name", "Event", "Alarm", "Door", "Blood", "Area", "Zone", "Course", "Spawn" }; private static readonly string[] CourseNodeDumpKeywords = new string[8] { "Dimension", "Layer", "Zone", "Area", "Node", "Course", "Local", "Position" }; private static readonly string[] SpawnDataDumpKeywords = new string[20] { "group", "type", "spawn", "enemy", "data", "id", "persistent", "node", "course", "area", "zone", "position", "rotation", "hibernate", "event", "wave", "blood", "door", "debug", "mode" }; private static readonly string[] NestedSpawnDataDumpKeywords = new string[10] { "EnemyGroup", "SpawnData", "CourseNode", "Area", "Zone", "GameData", "Blood", "Door", "Wave", "Event" }; private const bool EnableSpawnDiagnostics = true; private const int MaxSpawnDiagnosticsPerWindow = 20; private const float SpawnDiagnosticWindowSeconds = 10f; private const float ScanDelaySeconds = 0.2f; private const float CaptureWindowSeconds = 3f; private const float AlarmSessionQuietSeconds = 8f; private const float AgentDataResolveDelaySeconds = 0.2f; private const float AliveRefreshIntervalSeconds = 0.25f; private const float ClientFallbackWarmupSeconds = 20f; private static readonly HashSet<string> KnownDynamicEnemyIDs = new HashSet<string>(StringComparer.Ordinal) { "27", "28", "33", "39", "40", "43" }; private static readonly HashSet<int> KnownEnemyIds = new HashSet<int>(); private static readonly List<SpawnCaptureRecord> ActiveCaptureRecords = new List<SpawnCaptureRecord>(); private static readonly List<AgentCaptureRecord> ActiveAgentCaptureRecords = new List<AgentCaptureRecord>(); private static readonly SpawnTracker DynamicSpawnTracker = new SpawnTracker("Dynamic"); private static readonly SpawnTracker BloodDoorSpawnTracker = new SpawnTracker("BloodDoor"); private static readonly Dictionary<string, SpawnTracker> EnemyTrackerByKey = new Dictionary<string, SpawnTracker>(); private static readonly HashSet<string> CapturedAgentKeys = new HashSet<string>(); private static int SpawnDiagnosticsThisWindow; private static float SpawnDiagnosticWindowStart; private static bool IsCaptureWindowOpen; private static float CaptureEndTime; private static bool IsAlarmSessionOpen; private static bool IsSpawnDataBloodDoorRouteExclusive; private static float AlarmSessionEndTime; private static float NextAliveRefreshTime; private static float StartupTime = -1f; private static SpawnSource CurrentSessionSpawnSource = SpawnSource.UnknownDynamic; private static int NextFallbackAgentKey; private static Type EnemyAgentType; private static Type EnemyUpdateManagerType; private static MethodInfo FindObjectsOfTypeMethod; private static MethodInfo FindObjectsOfTypeAllMethod; private static MethodInfo GetInstanceIdMethod; private static PropertyInfo UnityObjectNameProperty; private static bool HasLoggedEnemyAgentShape; private static bool HasLoggedAliveProbeFailure; private static bool HasLoggedSpawnDiagnosticStack; private static bool HasRunSourceTypeDiscovery; private static int SourceInvestigationEventIndex; private static ConfigEntry<bool> DebugSourceModeConfig; private static ConfigEntry<bool> EnableBloodDoorCandidateClassificationConfig; private static ConfigEntry<string> BloodDoorSettingsNameKeywordsConfig; private static ConfigEntry<bool> EnableSpawnDataBloodDoorCandidateClassificationConfig; private static ConfigEntry<string> BloodDoorSpawnDataGroupTypesConfig; private static ConfigEntry<string> BloodDoorSpawnDataSpawnTypesConfig; private static ConfigEntry<string> ExcludedBloodDoorSpawnDataSpawnTypesConfig; private static ConfigEntry<bool> EnableEnemySetupProbeConfig; private static ConfigEntry<string> EnemySetupProbeNameKeywordsConfig; private static ConfigEntry<bool> EnableSpecialEnemyFallbackTrackingConfig; private static ConfigEntry<string> SpecialEnemyFallbackNameKeywordsConfig; private static ConfigEntry<string> SpecialEnemyFallbackTargetConfig; private static ConfigEntry<bool> SpecialEnemyFallbackRequireAgressiveModeConfig; private static ConfigEntry<bool> DynamicAutoHideEnabledConfig; private static ConfigEntry<float> DynamicAutoHideAfterSecondsConfig; internal static bool DebugModeEnabled => DebugMode; private static bool DebugSourceModeEnabled => DebugSourceModeConfig?.Value ?? false; private static bool BloodDoorCandidateClassificationEnabled => EnableBloodDoorCandidateClassificationConfig?.Value ?? false; private static bool SpawnDataBloodDoorCandidateClassificationEnabled => EnableSpawnDataBloodDoorCandidateClassificationConfig?.Value ?? false; private static bool EnemySetupProbeEnabled => EnableEnemySetupProbeConfig?.Value ?? false; private static bool SpecialEnemyFallbackTrackingEnabled => EnableSpecialEnemyFallbackTrackingConfig?.Value ?? false; private static bool DynamicAutoHideEnabled => DynamicAutoHideEnabledConfig?.Value ?? true; private static float DynamicAutoHideAfterSeconds => Math.Max(1f, DynamicAutoHideAfterSecondsConfig?.Value ?? 5f); internal static void ConfigureDynamicAutoHide(ConfigEntry<bool> dynamicAutoHideEnabled, ConfigEntry<float> dynamicAutoHideAfterSeconds) { DynamicAutoHideEnabledConfig = dynamicAutoHideEnabled; DynamicAutoHideAfterSecondsConfig = dynamicAutoHideAfterSeconds; } internal static void ConfigureSourceInvestigation(ConfigEntry<bool> debugSourceMode, ConfigEntry<bool> enableBloodDoorCandidateClassification, ConfigEntry<string> bloodDoorSettingsNameKeywords, ConfigEntry<bool> enableSpawnDataBloodDoorCandidateClassification, ConfigEntry<string> bloodDoorSpawnDataGroupTypes, ConfigEntry<string> bloodDoorSpawnDataSpawnTypes, ConfigEntry<string> excludedBloodDoorSpawnDataSpawnTypes, ConfigEntry<bool> enableEnemySetupProbe, ConfigEntry<string> enemySetupProbeNameKeywords, ConfigEntry<bool> enableSpecialEnemyFallbackTracking, ConfigEntry<string> specialEnemyFallbackNameKeywords, ConfigEntry<string> specialEnemyFallbackTarget, ConfigEntry<bool> specialEnemyFallbackRequireAgressiveMode) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown DebugSourceModeConfig = debugSourceMode; EnableBloodDoorCandidateClassificationConfig = enableBloodDoorCandidateClassification; BloodDoorSettingsNameKeywordsConfig = bloodDoorSettingsNameKeywords; EnableSpawnDataBloodDoorCandidateClassificationConfig = enableSpawnDataBloodDoorCandidateClassification; BloodDoorSpawnDataGroupTypesConfig = bloodDoorSpawnDataGroupTypes; BloodDoorSpawnDataSpawnTypesConfig = bloodDoorSpawnDataSpawnTypes; ExcludedBloodDoorSpawnDataSpawnTypesConfig = excludedBloodDoorSpawnDataSpawnTypes; EnableEnemySetupProbeConfig = enableEnemySetupProbe; EnemySetupProbeNameKeywordsConfig = enemySetupProbeNameKeywords; EnableSpecialEnemyFallbackTrackingConfig = enableSpecialEnemyFallbackTracking; SpecialEnemyFallbackNameKeywordsConfig = specialEnemyFallbackNameKeywords; SpecialEnemyFallbackTargetConfig = specialEnemyFallbackTarget; SpecialEnemyFallbackRequireAgressiveModeConfig = specialEnemyFallbackRequireAgressiveMode; if (DebugSourceModeEnabled) { ManualLogSource logSource = Plugin.LogSource; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(122, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[AST SourceInvestigation] BloodDoor investigation build enabled. survivalWaveCandidateEnabled="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(BloodDoorCandidateClassificationEnabled); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnDataCandidateEnabled="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(SpawnDataBloodDoorCandidateClassificationEnabled); } logSource.LogInfo(val); } } internal static void Apply(Harmony harmony) { //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Expected O, but got Unknown //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Expected O, but got Unknown //IL_037d: Unknown result type (might be due to invalid IL or missing references) //IL_0384: Expected O, but got Unknown //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_02ba: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Expected O, but got Unknown //IL_02cd: Expected O, but got Unknown //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Expected O, but got Unknown //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Expected O, but got Unknown //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Expected O, but got Unknown if (DebugMode) { Plugin.LogSource.LogInfo((object)"[Alarm Spawn Teller] Installing Enemies.EnemyGroup.Spawn patch..."); } EnemyAgentType = typeof(EnemyAgent); EnemyUpdateManagerType = typeof(EnemyUpdateManager); RunSourceTypeDiscoveryOnce(); bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val; if (EnemyAgentType == null) { Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find Enemies.EnemyAgent; spawn patch will still install, but enemy scan cannot run yet."); } else if (DebugMode) { ManualLogSource logSource = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(44, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found EnemyAgent type: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(EnemyAgentType.FullName); } logSource.LogInfo(val); } if (EnemyUpdateManagerType == null) { if (DebugMode) { Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find Enemies.EnemyUpdateManager; manager diagnostics unavailable."); } } else if (DebugMode) { ManualLogSource logSource2 = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(52, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found EnemyUpdateManager type: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(EnemyUpdateManagerType.FullName); } logSource2.LogInfo(val); } if (!InitializeUnityObjectAccess() && DebugMode) { Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Unity object reflection helpers unavailable; spawn patch will still install, but enemy scan/name fallback may be limited."); } Type typeFromHandle = typeof(EnemyGroup); if (typeFromHandle == null) { Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find Enemies.EnemyGroup."); return; } if (DebugMode) { ManualLogSource logSource3 = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(44, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found EnemyGroup type: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(typeFromHandle.FullName); } logSource3.LogInfo(val); } MethodInfo[] array = (from methodInfo4 in typeFromHandle.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where methodInfo4.Name == "Spawn" select methodInfo4).ToArray(); if (array.Length == 0) { Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find any Enemies.EnemyGroup.Spawn overloads."); LogSpawnLikeMethods(typeFromHandle); return; } MethodInfo[] array2; if (DebugMode) { array2 = array; foreach (MethodInfo method in array2) { ManualLogSource logSource4 = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(43, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found Spawn overload: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatMethodSignature(method)); } logSource4.LogInfo(val); } } if (!array.Any(HasSurvivalWaveParameter)) { Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find Enemies.EnemyGroup.Spawn overload with a SurvivalWave parameter."); } MethodInfo methodInfo = AccessTools.Method(typeof(EnemyGroupSpawnPatch), "Prefix", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(EnemyGroupSpawnPatch), "Postfix", (Type[])null, (Type[])null); if (methodInfo == null || methodInfo2 == null) { Plugin.LogSource.LogError((object)"[Alarm Spawn Teller] Could not find local prefix/postfix methods for Harmony patch."); return; } int num2 = 0; array2 = array; foreach (MethodInfo methodInfo3 in array2) { try { harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(methodInfo), new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num2++; if (DebugMode) { ManualLogSource logSource5 = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(72, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Patched Spawn overload for diagnostics/statistics: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatMethodSignature(methodInfo3)); } logSource5.LogInfo(val); } } catch (Exception ex) { ManualLogSource logSource6 = Plugin.LogSource; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(54, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[Alarm Spawn Teller] Failed to patch Spawn overload "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(FormatMethodSignature(methodInfo3)); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex); } logSource6.LogError(val2); } } ManualLogSource logSource7 = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(57, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Patched "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" EnemyGroup.Spawn overloads."); } logSource7.LogInfo(val); PatchEnemyAgentSetup(harmony); if (DebugMode) { SnapshotExistingEnemies("initial"); } } private static void LogSpawnLikeMethods(Type enemyGroupType) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown MethodInfo[] array = (from methodInfo in enemyGroupType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where methodInfo.Name.Contains("Spawn", StringComparison.OrdinalIgnoreCase) select methodInfo).ToArray(); if (array.Length == 0) { Plugin.LogSource.LogWarning((object)"No method names containing Spawn were found on Enemies.EnemyGroup."); return; } MethodInfo[] array2 = array; bool flag = default(bool); foreach (MethodInfo method in array2) { ManualLogSource logSource = Plugin.LogSource; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(44, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Found Enemies.EnemyGroup spawn-like method: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatMethodSignature(method)); } logSource.LogInfo(val); } } private static void PatchEnemyAgentSetup(Harmony harmony) { //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Expected O, but got Unknown //IL_01dd: Unknown result type (might be due to invalid IL or missing references) //IL_01e4: Expected O, but got Unknown //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Expected O, but got Unknown //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Expected O, but got Unknown if (EnemyAgentType == null) { Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Cannot patch EnemyAgent.Setup because Enemies.EnemyAgent was not found."); return; } MethodInfo[] array = (from methodInfo3 in EnemyAgentType.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) where methodInfo3.Name == "Setup" select methodInfo3).ToArray(); if (array.Length == 0) { Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not find any Enemies.EnemyAgent.Setup overloads."); return; } bool flag2 = default(bool); MethodInfo[] array2; BepInExInfoLogInterpolatedStringHandler val; if (DebugMode) { array2 = array; foreach (MethodInfo method in array2) { bool flag = HasEnemySpawnDataParameter(method); ManualLogSource logSource = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(76, 2, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found EnemyAgent.Setup overload: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatMethodSignature(method)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; has pEnemySpawnData="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(flag); } logSource.LogInfo(val); } } MethodInfo methodInfo = AccessTools.Method(typeof(EnemyGroupSpawnPatch), "EnemyAgentSetupPostfix", (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.LogSource.LogError((object)"[Alarm Spawn Teller] Could not find local EnemyAgentSetupPostfix method."); return; } int num2 = 0; array2 = array; foreach (MethodInfo methodInfo2 in array2) { try { harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(methodInfo), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); num2++; if (DebugMode) { ManualLogSource logSource2 = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(56, 1, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Patched EnemyAgent.Setup overload: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatMethodSignature(methodInfo2)); } logSource2.LogInfo(val); } } catch (Exception ex) { ManualLogSource logSource3 = Plugin.LogSource; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(65, 2, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[Alarm Spawn Teller] Failed to patch EnemyAgent.Setup overload "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(FormatMethodSignature(methodInfo2)); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<Exception>(ex); } logSource3.LogError(val2); } } ManualLogSource logSource4 = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(57, 1, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Patched "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" EnemyAgent.Setup overloads."); } logSource4.LogInfo(val); } private static bool InitializeUnityObjectAccess() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown Type typeFromHandle = typeof(Object); if (DebugMode) { ManualLogSource logSource = Plugin.LogSource; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(49, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Found Unity object type: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(typeFromHandle.FullName); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ["); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(typeFromHandle.Assembly.GetName().Name); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("]"); } logSource.LogInfo(val); } FindObjectsOfTypeMethod = typeFromHandle.GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(delegate(MethodInfo method) { ParameterInfo[] parameters = method.GetParameters(); return method.Name == "FindObjectsOfType" && parameters.Length == 1 && parameters[0].ParameterType == typeof(Type); }); GetInstanceIdMethod = typeFromHandle.GetMethod("GetInstanceID", BindingFlags.Instance | BindingFlags.Public); UnityObjectNameProperty = typeFromHandle.GetProperty("name", BindingFlags.Instance | BindingFlags.Public); FindObjectsOfTypeAllMethod = typeof(Resources).GetMethods(BindingFlags.Static | BindingFlags.Public).FirstOrDefault(delegate(MethodInfo method) { ParameterInfo[] parameters = method.GetParameters(); return method.Name == "FindObjectsOfTypeAll" && parameters.Length == 1 && parameters[0].ParameterType == typeof(Type); }); if (FindObjectsOfTypeMethod == null || FindObjectsOfTypeAllMethod == null || GetInstanceIdMethod == null) { if (DebugMode) { Plugin.LogSource.LogWarning((object)"[Alarm Spawn Teller] Could not initialize Unity object reflection helpers."); } return false; } if (DebugMode) { Plugin.LogSource.LogInfo((object)"[Alarm Spawn Teller] Initialized Unity object reflection helpers."); } return true; } private static bool HasSurvivalWaveParameter(MethodBase method) { return method.GetParameters().Any(IsSurvivalWaveParameter); } private static bool HasEnemySpawnDataParameter(MethodBase method) { return method.GetParameters().Any(IsEnemySpawnDataParameter); } private static bool IsSurvivalWaveParameter(ParameterInfo parameter) { string? obj = parameter.ParameterType.FullName ?? parameter.ParameterType.Name; string text = parameter.Name ?? string.Empty; if (!obj.Contains("SurvivalWave", StringComparison.OrdinalIgnoreCase)) { return text.Contains("survivalWave", StringComparison.OrdinalIgnoreCase); } return true; } private static bool IsEnemySpawnDataParameter(ParameterInfo parameter) { if (!IsParameterNamedOrTyped(parameter, "pEnemySpawnData") && !IsParameterNamedOrTyped(parameter, "enemySpawnData")) { return IsParameterNamedOrTyped(parameter, "spawnData"); } return true; } private static bool IsEnemyGroupSpawnDataParameter(ParameterInfo parameter) { return IsParameterNamedOrTyped(parameter, "pEnemyGroupSpawnData"); } private static object GetSurvivalWaveArgument(MethodBase originalMethod, object[] args) { if (originalMethod == null || args == null) { return null; } ParameterInfo[] parameters = originalMethod.GetParameters(); for (int i = 0; i < parameters.Length && i < args.Length; i++) { if (IsSurvivalWaveParameter(parameters[i])) { return args[i]; } } return null; } private static bool IsAlarmWaveSpawn(MethodBase originalMethod, object[] args) { if (GetSurvivalWaveArgument(originalMethod, args) == null) { return false; } if (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "groupType"), out var _, out var value)) { return string.Equals(value?.ToString(), "Survival", StringComparison.OrdinalIgnoreCase); } return false; } private static SpawnSource InferSpawnSource(MethodBase originalMethod, object[] args) { object survivalWaveArgument = GetSurvivalWaveArgument(originalMethod, args); if (survivalWaveArgument != null && BloodDoorCandidateClassificationEnabled && EvaluateBloodDoorCandidate(survivalWaveArgument).CandidateMatched) { return SpawnSource.BloodDoor; } if (SpawnDataBloodDoorCandidateClassificationEnabled && TryGetParameterValue(originalMethod, args, IsEnemyGroupSpawnDataParameter, out var _, out var value) && EvaluateSpawnDataBloodDoorCandidate(value).CandidateMatched) { return SpawnSource.BloodDoor; } if (IsAlarmWaveSpawn(originalMethod, args)) { return SpawnSource.SurvivalWave; } if (survivalWaveArgument != null) { return SpawnSource.SurvivalWave; } return SpawnSource.UnknownDynamic; } private static string FormatSpawnSourceLabel(SpawnSource source) { return Localizer.GetSourceDisplayName(source); } private static bool TryGetSpawnDataArgument(MethodBase originalMethod, object[] args, out object spawnData) { ParameterInfo parameter; return TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "pEnemyGroupSpawnData") || IsParameterNamedOrTyped(parameter2, "spawnData"), out parameter, out spawnData); } private static bool TryGetEnemySpawnDataArgument(MethodBase originalMethod, object[] args, out object spawnData) { ParameterInfo parameter; return TryGetParameterValue(originalMethod, args, IsEnemySpawnDataParameter, out parameter, out spawnData); } private static bool TryGetParameterValue(MethodBase originalMethod, object[] args, Func<ParameterInfo, bool> predicate, out ParameterInfo parameter, out object value) { parameter = null; value = null; if (originalMethod == null || args == null) { return false; } ParameterInfo[] parameters = originalMethod.GetParameters(); for (int i = 0; i < parameters.Length && i < args.Length; i++) { if (predicate(parameters[i])) { parameter = parameters[i]; value = args[i]; return true; } } return false; } private static bool IsParameterNamedOrTyped(ParameterInfo parameter, string keyword) { string? obj = parameter.ParameterType.FullName ?? parameter.ParameterType.Name; string text = parameter.Name ?? string.Empty; if (!obj.Contains(keyword, StringComparison.OrdinalIgnoreCase)) { return text.Contains(keyword, StringComparison.OrdinalIgnoreCase); } return true; } private static void LogSpawnDiagnostic(MethodBase originalMethod, object[] args) { //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Expected O, but got Unknown //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Expected O, but got Unknown //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Expected O, but got Unknown if (!DebugMode || !CanLogSpawnDiagnostic()) { return; } string text = ((originalMethod == null) ? "<unknown>" : FormatMethodSignature(originalMethod)); ParameterInfo parameter; object value; string text2 = (TryGetParameterValue(originalMethod, args, IsSurvivalWaveParameter, out parameter, out value) ? $"present, null={value == null}" : "not present"); object value2; string text3 = (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "spawnType"), out parameter, out value2) ? FormatValueSummary(value2) : "<not present>"); object value3; string text4 = (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "groupType"), out parameter, out value3) ? FormatValueSummary(value3) : "<not present>"); object value4; string text5 = (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "populationScore"), out parameter, out value4) ? FormatValueSummary(value4) : "<not present>"); object value5; string text6 = (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "persistentGameDataID"), out parameter, out value5) ? FormatValueSummary(value5) : "<not present>"); SpawnSource spawnSource = InferSpawnSource(originalMethod, args); ManualLogSource logSource = Plugin.LogSource; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(177, 9, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Spawn diagnostic: overload="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; survivalWave="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnType="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text3); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; groupType="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text4); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; populationScore="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text5); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; persistentGameDataID="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text6); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; activeSession="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(IsAlarmSessionOpen); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; activeCapture="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(IsCaptureWindowOpen); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; inferredSource="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSource>(spawnSource); } logSource.LogInfo(val); if (TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "pEnemyGroupSpawnData") || IsParameterNamedOrTyped(parameter2, "spawnData"), out parameter, out var value6)) { ManualLogSource logSource2 = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(49, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Spawn diagnostic spawnData: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatObjectMembersSummary(value6)); } logSource2.LogInfo(val); } if (!HasLoggedSpawnDiagnosticStack) { HasLoggedSpawnDiagnosticStack = true; ManualLogSource logSource3 = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(45, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[Alarm Spawn Teller] Spawn diagnostic stack: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(Environment.StackTrace); } logSource3.LogInfo(val); } } private static void LogSourceInvestigation(MethodBase originalMethod, object[] args, SpawnPatchState patchState) { //IL_0432: Unknown result type (might be due to invalid IL or missing references) //IL_0439: Expected O, but got Unknown //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Expected O, but got Unknown //IL_0339: Unknown result type (might be due to invalid IL or missing references) //IL_0340: Expected O, but got Unknown if (!DebugSourceModeEnabled && !BloodDoorCandidateClassificationEnabled && !SpawnDataBloodDoorCandidateClassificationEnabled) { return; } int num = Interlocked.Increment(ref SourceInvestigationEventIndex); bool flag4 = default(bool); try { TryGetParameterValue(originalMethod, args, IsSurvivalWaveParameter, out var parameter, out var value); object value2; bool flag = TryGetParameterValue(originalMethod, args, IsEnemyGroupSpawnDataParameter, out parameter, out value2); if (value != null) { LogBloodDoorCandidate(num, value); } if (flag) { SpawnDataRouteEvaluation route = patchState?.SpawnDataRoute ?? ResolveSpawnDataRoute(value2); LogSpawnDataBloodDoorCandidate(num, route); } if (DebugSourceModeEnabled) { RunSourceTypeDiscoveryOnce(); string text = ((originalMethod == null) ? "<unknown>" : FormatMethodSignature(originalMethod)); string parameterSummary = GetParameterSummary(originalMethod, args, "groupType"); string parameterSummary2 = GetParameterSummary(originalMethod, args, "spawnType"); string parameterSummary3 = GetParameterSummary(originalMethod, args, "persistentGameDataID"); string parameterSummary4 = GetParameterSummary(originalMethod, args, "populationScore"); string parameterSummary5 = GetParameterSummary(originalMethod, args, "position"); string parameterSummary6 = GetParameterSummary(originalMethod, args, "rotation"); TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "courseNode"), out parameter, out var value3); TryGetParameterValue(originalMethod, args, (ParameterInfo parameter2) => IsParameterNamedOrTyped(parameter2, "targetReplicator"), out parameter, out var value4); SpawnSource spawnSource = InferSpawnSource(originalMethod, args); SpawnSourceKind spawnSourceKind = GetSpawnSourceKind(CurrentSessionSpawnSource); SpawnTracker trackerForSource = GetTrackerForSource(CurrentSessionSpawnSource); SpawnDataRouteEvaluation obj = patchState?.SpawnDataRoute; bool flag2 = obj?.RouteExclusive ?? false; bool flag3 = obj?.SkippedDynamicBecauseBloodDoorCandidate ?? false; string text2 = obj?.TargetTracker ?? trackerForSource.Name; ManualLogSource logSource = Plugin.LogSource; BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(254, 14, ref flag4); if (flag4) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[AST SourceInvestigation] eventIndex="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; method="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; groupType="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnType="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; persistentGameDataID="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary3); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; populationScore="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary4); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; survivalWavePresent="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(value != null); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; survivalWaveType="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatRuntimeType(value)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; position="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary5); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; rotation="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(parameterSummary6); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; courseNodePresent="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(value3 != null); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; courseNodeType="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(FormatRuntimeType(value3)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; targetReplicatorPresent="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(value4 != null); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; inferredSource="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSource>(spawnSource); } logSource.LogInfo(val); ManualLogSource logSource2 = Plugin.LogSource; val = new BepInExInfoLogInterpolatedStringHandler(173, 7, ref flag4); if (flag4) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[AST SpawnRoute] eventIndex="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(num); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; currentSessionSource="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSource>(CurrentSessionSpawnSource); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; currentSourceKind="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSourceKind>(spawnSourceKind); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; tracker="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; routeExclusive="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(flag2); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; skippedDynamicBecauseBloodDoorCandidate="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(flag3); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; bloodDoorClassificationEnabled="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(BloodDoorCandidateClassificationEnabled); } logSource2.LogInfo(val); DumpFocusedMembers("[AST SurvivalWaveDump]", num, "survivalWave", value, (IReadOnlyCollection<string>)(object)SurvivalWaveDumpKeywords); DumpSurvivalWaveRelatedObjects(num, value); DumpFocusedMembers("[AST CourseNodeDump]", num, "courseNode", value3, (IReadOnlyCollection<string>)(object)CourseNodeDumpKeywords); if (flag) { DumpEnemyGroupSpawnData(num, value2); } } } catch (Exception ex) { ManualLogSource logSource3 = Plugin.LogSource; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(48, 3, ref flag4); if (flag4) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("[AST SourceInvestigation] eventIndex="); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<int>(num); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("; failed="); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.GetType().Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message); } logSource3.LogWarning(val2); } } private static void LogSpawnDataBloodDoorCandidate(int eventIndex, SpawnDataRouteEvaluation route) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown SpawnDataBloodDoorCandidateEvaluation candidate = route.Candidate; ManualLogSource logSource = Plugin.LogSource; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(444, 21, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[AST SpawnDataBloodDoorCandidate] eventIndex="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(eventIndex); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; groupType="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.GroupType); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; debugEnemyMode="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.DebugEnemyMode); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnType="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.SpawnType); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; persistentGameDataID="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.PersistentGameDataID); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; persistentWavePopDataID="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.PersistentWavePopDataID); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; survivalWaveReplicatorKey="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.SurvivalWaveReplicatorKey); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; courseNodeIDPlusOne="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.CourseNodeIDPlusOne); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; groupTypeMatched="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(candidate.GroupTypeMatched); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnTypeMatched="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(candidate.SpawnTypeMatched); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; spawnTypeExcluded="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(candidate.SpawnTypeExcluded); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; allowedSpawnTypes="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.AllowedSpawnTypes); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; excludedSpawnTypes="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(candidate.ExcludedSpawnTypes); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; candidateMatched="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(candidate.CandidateMatched); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; candidateSourceKind="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSourceKind>(route.CandidateSourceKind); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; inheritedSourceKind="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSourceKind>(route.InheritedSessionSourceKind); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; forcedNotBloodDoor="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(route.ForcedNotBloodDoor); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; candidateReason="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(route.CandidateReason); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; finalSourceKind="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<SpawnSourceKind>(route.FinalSourceKind); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; targetTracker="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(route.TargetTracker); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; classificationEnabled="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<bool>(SpawnDataBloodDoorCandidateClassificationEnabled); } logSource.LogInfo(val); } private static SpawnDataRouteEvaluation ResolveSpawnDataRoute(object spawnData) { SpawnDataBloodDoorCandidateEvaluation spawnDataBloodDoorCandidateEvaluation = EvaluateSpawnDataBloodDoorCandidate(spawnData); SpawnSourceKind spawnSourceKind = (IsAlarmSessionOpen ? GetSpawnSourceKind(CurrentSessionSpawnSource) : SpawnSourceKind.None); SpawnSourceKind candidateSourceKind = (spawnDataBloodDoorCandidateEvaluation.CandidateMatched ? SpawnSourceKind.BloodDoor : SpawnSourceKind.None); bool forcedNotBloodDoor = !spawnDataBloodDoorCandidateEvaluation.CandidateMatched; SpawnSourceKind spawnSourceKind2 = (spawnDataBloodDoorCandidateEvaluation.CandidateMatched ? SpawnSourceKind.BloodDoor : (spawnSourceKind switch { SpawnSourceKind.BloodDoor => SpawnSourceKind.Dynamic, SpawnSourceKind.Dynamic => SpawnSourceKind.Dynamic, _ => SpawnSourceKind.None, })); SpawnDataRouteEvaluation spawnDataRouteEvaluation = new SpawnDataRouteEvaluation(); spawnDataRouteEvaluation.Candidate = spawnDataBloodDoorCandidateEvaluation; spawnDataRouteEvaluation.CandidateSourceKind = candidateSourceKind; spawnDataRouteEvaluation.InheritedSessionSourceKind = spawnSourceKind; spawnDataRouteEvaluation.FinalSourceKind = spawnSourceKind2; spawnDataRouteEvaluation.ForcedNotBloodDoor = forcedNotBloodDoor; spawnDataRouteEvaluation.RouteExclusive = spawnDataBloodDoorCandidateEvaluation.CandidateMatched; spawnDataRouteEvaluation.SkippedDynamicBecauseBloodDoorCandidate = spawnDataBloodDoorCandidateEvaluation.CandidateMatched; spawnDataRouteEvaluation.CandidateReason = GetSpawnDataCandidateReason(spawnDataBloodDoorCandidateEvaluation); SpawnDataRouteEvaluation spawnDataRouteEvaluation2 = spawnDataRouteEvaluation; spawnDataRouteEvaluation2.TargetTracker = spawnSourceKind2 switch { SpawnSourceKind.BloodDoor => BloodDoorSpawnTracker.Name, SpawnSourceKind.Dynamic => DynamicSpawnTracker.Name, _ => "<none>", }; return spawnDataRouteEvaluation; } private static string GetSpawnDataCandidateReason(SpawnDataBloodDoorCandidateEvaluation candidate) { if (candidate == null) { return "candidate unavailable"; } if (candidate.SpawnTypeExcluded) { return "spawn type excluded"; } if (!SpawnDataBloodDoorCandidateClassificationEnabled) { return "classification disabled"; } if (!candidate.GroupTypeMatched) { return "group type not matched"; } if (!candidate.SpawnTypeMatched) { return "spawn type not allowed"; } if (!candidate.CandidateMatched) { return "candidate conditions not met"; } return "candidate matched"; } private static void ApplySpawnDataRoute(SpawnDataRouteEvaluation route) { if (route != null) { IsSpawnDataBloodDoorRouteExclusive = route.Candidate.CandidateMatched; if (!route.Candidate.CandidateMatched && CurrentSessionSpawnSource == SpawnSource.BloodDoor) { CurrentSessionSpawnSource = SpawnSource.UnknownDynamic; } } } private static SpawnDataBloodDoorCandidateEvaluation EvaluateSpawnDataBloodDoorCandidate(object spawnData) { SpawnDataBloodDoorCandidateEvaluation result = new SpawnDataBloodDoorCandidateEvaluation(); if (spawnData == null) { return result; } result.GroupType = FormatCandidateValue(GetFirstMemberValue(spawnData, "groupType", "GroupType", "m_groupType")); result.DebugEnemyMode = FormatCandidateValue(GetFirstMemberValue(spawnData, "debugEnemyMode", "DebugEnemyMode", "m_debugEnemyMode")); result.SpawnType = FormatCandidateValue(GetFirstMemberValue(spawnData, "spawnType", "SpawnType", "m_spawnType")); result.PersistentGameDataID = FormatCandidateValue(GetFirstMemberValue(spawnData, "persistentGameDataID", "PersistentGameDataID", "m_persistentGameDataID")); result.PersistentWavePopDataID = FormatCandidateValue(GetFirstMemberValue(spawnData, "persistentWavePopDataID", "PersistentWavePopDataID", "m_persistentWavePopDataID")); result.SurvivalWaveReplicatorKey = FormatCandidateValue(GetFirstMemberValue(spawnData, "survivalWaveReplicatorKey", "SurvivalWaveReplicatorKey", "m_survivalWaveReplicatorKey")); result.CourseNodeIDPlusOne = FormatCandidateValue(GetFirstMemberValue(spawnData, "courseNodeIDPlusOne", "CourseNodeIDPlusOne", "m_courseNodeIDPlusOne")); string[] configuredValues = GetConfiguredValues(BloodDoorSpawnDataGroupTypesConfig?.Value); string[] configuredValues2 = GetConfiguredValues(BloodDoorSpawnDataSpawnTypesConfig?.Value); string[] configuredValues3 = GetConfiguredValues(ExcludedBloodDoorSpawnDataSpawnTypesConfig?.Value); result.AllowedSpawnTypes = FormatConfiguredValues(configuredValues2); result.ExcludedSpawnTypes = FormatConfiguredValues(configuredValues3); result.GroupTypeMatched = configuredValues.Length != 0 && !string.IsNullOrWhiteSpace(result.GroupType) && result.GroupType != "<null>" && configuredValues.Any((string groupType) => string.Equals(groupType, result.GroupType, StringComparison.OrdinalIgnoreCase)); result.SpawnTypeMatched = configuredValues2.Length != 0 && !string.IsNullOrWhiteSpace(result.SpawnType) && result.SpawnType != "<null>" && configuredValues2.Any((string spawnType) => string.Equals(spawnType, result.SpawnType, StringComparison.OrdinalIgnoreCase)); result.SpawnTypeExcluded = configuredValues3.Length != 0 && !string.IsNullOrWhiteSpace(result.SpawnType) && result.SpawnType != "<null>" && configuredValues3.Any((string spawnType) => string.Equals(spawnType, result.SpawnType, StringComparison.OrdinalIgnoreCase)); result.CandidateMatched = SpawnDataBloodDoorCandidateClassificationEnabled && result.GroupTypeMatched && result.SpawnTypeMatched && !result.SpawnTypeExcluded; return result; } private static string[] GetConfiguredValues(string configuredValues) { if (string.IsNullOrWhiteSpace(configuredValues)) { return Array.Empty<string>(); } return (from value in configuredValues.Split(',', StringSplitOptions.RemoveEmptyEntries) select value.Trim() into value where !string.IsNullOrWhiteSpace(value) select value).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToArray(); } private static string FormatConfiguredValues(string[] configuredValues) { if (configuredValues != null && configuredValues.Length != 0) { return string.Join(",", configuredValues); } return "<empty>"; } private static void LogBloodDoorCandidate(int eventIndex, object survivalWave) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Expected O, but got Unknown BloodDoorCandidateEvaluation bloodDoorCandidateEvaluation = EvaluateBloodDoorCandidate(survivalWave); SpawnSourceKind spawnSourceKind = ((bloodDoorCandidateEvaluation.CandidateMatched && BloodDoorCandidateClassificationEnabled) ? SpawnSourceKind.BloodDoor : GetSpawnSourceKind(CurrentSessionSpawnSource)); ManualLogSource logSource = Plugin.LogSource; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(239, 12, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("[AST BloodDoorCandidate] eventIndex="); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<int>(eventIndex); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("; settings.name=");