Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BossRules v1.0.6
BossRules.dll
Decompiled a week 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.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using SoftReferenceableAssets; using TMPro; using UnityEngine; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("BossRules")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("BossRules")] [assembly: AssemblyCopyright("Copyright (c) 2026")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("E72C88F7-8DC7-45B4-A56E-5E25F182EF28")] [assembly: AssemblyFileVersion("1.0.6")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.6.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BossRules { [BepInPlugin("sighsorry.BossRules", "BossRules", "1.0.6")] public sealed class BossRulesPlugin : BaseUnityPlugin { public enum Toggle { On = 1, Off = 0 } private sealed class ConfigurationManagerAttributes { public int? Order; } internal const string ModName = "BossRules"; internal const string ModVersion = "1.0.6"; internal const string Author = "sighsorry"; internal const string ModGUID = "sighsorry.BossRules"; internal const string AltarYamlFileName = "BossRules.altar.yml"; internal const string AltarReferenceYamlFileName = "BossRules.altar.reference.yml"; internal const string RulesYamlFileName = "BossRules.yml"; internal const string ForsakenPowersYamlFileName = "BossRules.forsakenPowers.yml"; private const float FileReloadDebounceSeconds = 0.25f; internal static readonly ManualLogSource BossRulesLogger = Logger.CreateLogSource("BossRules"); private static ConfigSync? _configSync; private readonly Harmony _harmony = new Harmony("sighsorry.BossRules"); private CustomSyncedValue<string> _syncedAltarYaml; private CustomSyncedValue<string> _syncedRulesYaml; private CustomSyncedValue<string> _syncedForsakenPowersYaml; private FileSystemWatcher? _watcher; private float _reloadDueAt = -1f; private int _altarYamlReloadRequested; private int _rulesYamlReloadRequested; private int _forsakenPowersYamlReloadRequested; private bool _reloadAltarYaml; private bool _reloadRulesYaml; private bool _reloadForsakenPowersYaml; private ConfigEntry<Toggle> _lockConfiguration; internal static ConfigSync ConfigSync => _configSync ?? throw new InvalidOperationException("ServerSync has not been initialized yet."); internal static string ConfigDirectoryPath => Path.Combine(Paths.ConfigPath, "BossRules"); internal static string AltarYamlFilePath => Path.Combine(ConfigDirectoryPath, "BossRules.altar.yml"); internal static string AltarReferenceYamlFilePath => Path.Combine(ConfigDirectoryPath, "BossRules.altar.reference.yml"); internal static string RulesYamlFilePath => Path.Combine(ConfigDirectoryPath, "BossRules.yml"); internal static string ForsakenPowersYamlFilePath => Path.Combine(ConfigDirectoryPath, "BossRules.forsakenPowers.yml"); internal static bool IsSourceOfTruth => ConfigSync.IsSourceOfTruth; internal static bool IsRuntimeServer() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } private void Awake() { EnsureServerSyncInitialized(); Directory.CreateDirectory(ConfigDirectoryPath); AltarConfigurationFiles.EnsureDefaultFiles(); BossRuleConfigurationFiles.EnsureDefaultFile(); ForsakenPowerConfigurationFiles.EnsureDefaultFile(); BindConfiguration(); _syncedAltarYaml = new CustomSyncedValue<string>(ConfigSync, "altar-yaml", "", 50); _syncedAltarYaml.ValueChanged += HandleSyncedAltarYamlChanged; _syncedRulesYaml = new CustomSyncedValue<string>(ConfigSync, "rules-yaml", "", 60); _syncedRulesYaml.ValueChanged += HandleSyncedRulesYamlChanged; _syncedForsakenPowersYaml = new CustomSyncedValue<string>(ConfigSync, "forsaken-powers-yaml", "", 65); _syncedForsakenPowersYaml.ValueChanged += HandleSyncedForsakenPowersYamlChanged; ConfigSync.SourceOfTruthChanged += HandleSourceOfTruthChanged; LoadLocalAltarYamlAndPublish("startup"); LoadLocalRulesYamlAndPublish("startup"); LoadLocalForsakenPowersYamlAndPublish("startup"); _harmony.PatchAll(typeof(BossRulesPlugin).Assembly); Localizer.Initialize(this); BossStonePerPlayerRuntime.EnsureRpcRegistered(); DespawnRulesManager.EnsureMessageRpcRegistered(); BossRulesConsoleCommands.Register(); InitializeWatcher(); ((BaseUnityPlugin)this).Config.Save(); } private void Update() { ProcessQueuedYamlReload(); Localizer.ProcessDeferredLoad(); DataForgeStatusEffectBridge.ProcessDeferredSubscription(); AltarRuntime.ProcessPendingAltarSummonMarkers(); AltarRuntime.ProcessPendingQueenDungeonRooms(); AltarRuntime.ProcessDeferredReapply(); AltarReferenceGenerator.TryAutoRefreshReferenceConfigurationFile(); ForsakenPowerRuntime.ProcessDeferredApply(); BossStonePerPlayerRuntime.EnsureRpcRegistered(); BossStonePerPlayerRuntime.ProcessPendingSacrificeRequests(); BossStonePerPlayerRuntime.ProcessPendingResetRequests(); DespawnRulesManager.EnsureMessageRpcRegistered(); DespawnRulesManager.ExecuteServerTick(); BossTamedPressureRuntime.ExecuteServerTick(); } private void OnDestroy() { if (_configSync != null) { ConfigSync.SourceOfTruthChanged -= HandleSourceOfTruthChanged; } _syncedAltarYaml.ValueChanged -= HandleSyncedAltarYamlChanged; _syncedRulesYaml.ValueChanged -= HandleSyncedRulesYamlChanged; _syncedForsakenPowersYaml.ValueChanged -= HandleSyncedForsakenPowersYamlChanged; _watcher?.Dispose(); _watcher = null; Interlocked.Exchange(ref _altarYamlReloadRequested, 0); Interlocked.Exchange(ref _rulesYamlReloadRequested, 0); Interlocked.Exchange(ref _forsakenPowersYamlReloadRequested, 0); ClearPendingYamlReloads(); AltarRuntime.Shutdown(); BossStonePerPlayerRuntime.Shutdown(); DespawnRulesManager.ShutdownMessages(); ForsakenPowerSelectionRuntime.Shutdown(); AltarReferenceGenerator.ShutdownAutoRefresh(); BossRulesManager.ClearRuntimeState(); BossRulesRuntime.Reset(); ForsakenPowerRuntime.Reset(); DataForgeStatusEffectBridge.Shutdown(); Localizer.Shutdown(); _harmony.UnpatchSelf(); ((BaseUnityPlugin)this).Config.Save(); } private static void EnsureServerSyncInitialized() { if (_configSync == null) { _configSync = new ConfigSync("sighsorry.BossRules") { DisplayName = "BossRules", CurrentVersion = "1.0.6", MinimumRequiredVersion = "1.0.6" }; } } private void BindConfiguration() { bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; try { _lockConfiguration = BindConfigEntry("1 - General", "Lock Configuration", Toggle.On, "If on, synced configuration can be changed by server admins only.", synchronizedSetting: true, 200); BossRulesConfig.Bind(this); ConfigSync.AddLockingConfigEntry<Toggle>(_lockConfiguration); } finally { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } internal ConfigEntry<T> BindConfigEntry<T>(string group, string name, T value, string description, bool synchronizedSetting = true, int? configManagerOrder = null) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown ConfigDescription val = new ConfigDescription(description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"), (AcceptableValueBase)null, BuildConfigDescriptionTags(configManagerOrder)); ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, val); ConfigSync.AddConfigEntry<T>(val2).SynchronizedConfig = synchronizedSetting; return val2; } private static object[] BuildConfigDescriptionTags(int? configManagerOrder) { if (!configManagerOrder.HasValue) { return Array.Empty<object>(); } return new object[1] { new ConfigurationManagerAttributes { Order = configManagerOrder.Value } }; } private void InitializeWatcher() { _watcher = new FileSystemWatcher(ConfigDirectoryPath, "*.yml") { IncludeSubdirectories = false, NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime) }; _watcher.Changed += QueueYamlReload; _watcher.Created += QueueYamlReload; _watcher.Deleted += QueueYamlReload; _watcher.Renamed += QueueYamlReload; _watcher.EnableRaisingEvents = true; } private void QueueYamlReload(object sender, FileSystemEventArgs args) { QueueYamlReloadForFile(Path.GetFileName(args.FullPath)); if (args is RenamedEventArgs e) { QueueYamlReloadForFile(Path.GetFileName(e.OldFullPath)); } } private void QueueYamlReloadForFile(string fileName) { if (string.Equals(fileName, "BossRules.altar.yml", StringComparison.OrdinalIgnoreCase)) { Interlocked.Exchange(ref _altarYamlReloadRequested, 1); } else if (string.Equals(fileName, "BossRules.yml", StringComparison.OrdinalIgnoreCase)) { Interlocked.Exchange(ref _rulesYamlReloadRequested, 1); } else if (string.Equals(fileName, "BossRules.forsakenPowers.yml", StringComparison.OrdinalIgnoreCase)) { Interlocked.Exchange(ref _forsakenPowersYamlReloadRequested, 1); } } private void ProcessQueuedYamlReload() { bool flag = false; if (Interlocked.Exchange(ref _altarYamlReloadRequested, 0) != 0) { _reloadAltarYaml = true; flag = true; } if (Interlocked.Exchange(ref _rulesYamlReloadRequested, 0) != 0) { _reloadRulesYaml = true; flag = true; } if (Interlocked.Exchange(ref _forsakenPowersYamlReloadRequested, 0) != 0) { _reloadForsakenPowersYaml = true; flag = true; } if (flag) { _reloadDueAt = (IsSourceOfTruth ? (Time.realtimeSinceStartup + 0.25f) : (-1f)); } if (!IsSourceOfTruth) { _reloadDueAt = -1f; ClearPendingYamlReloads(); } else if (!(_reloadDueAt < 0f) && !(Time.realtimeSinceStartup < _reloadDueAt)) { _reloadDueAt = -1f; bool reloadAltarYaml = _reloadAltarYaml; bool reloadRulesYaml = _reloadRulesYaml; bool reloadForsakenPowersYaml = _reloadForsakenPowersYaml; ClearPendingYamlReloads(); if (reloadAltarYaml) { LoadLocalAltarYamlAndPublish("file change"); } if (reloadRulesYaml) { LoadLocalRulesYamlAndPublish("file change"); } if (reloadForsakenPowersYaml) { LoadLocalForsakenPowersYamlAndPublish("file change"); } } } private void ClearPendingYamlReloads() { _reloadAltarYaml = false; _reloadRulesYaml = false; _reloadForsakenPowersYaml = false; } private void HandleSourceOfTruthChanged(bool sourceOfTruth) { if (sourceOfTruth) { AltarReferenceGenerator.ResetAutoRefresh(); LoadLocalAltarYamlAndPublish("authority change"); LoadLocalRulesYamlAndPublish("authority change"); LoadLocalForsakenPowersYamlAndPublish("authority change"); } else { ApplyAltarYaml(_syncedAltarYaml.Value ?? "", "server sync"); ApplyRulesYaml(_syncedRulesYaml.Value ?? "", "server sync"); ApplyForsakenPowersYaml(_syncedForsakenPowersYaml.Value ?? "", "server sync"); } } private void HandleSyncedAltarYamlChanged() { if (!IsSourceOfTruth) { ApplyAltarYaml(_syncedAltarYaml.Value ?? "", "server sync"); } } private void HandleSyncedRulesYamlChanged() { if (!IsSourceOfTruth) { ApplyRulesYaml(_syncedRulesYaml.Value ?? "", "server sync"); } } private void HandleSyncedForsakenPowersYamlChanged() { if (!IsSourceOfTruth) { ApplyForsakenPowersYaml(_syncedForsakenPowersYaml.Value ?? "", "server sync"); } } private void LoadLocalAltarYamlAndPublish(string source) { AltarConfigurationFiles.EnsureDefaultFiles(); string text; try { text = File.ReadAllText(AltarYamlFilePath); } catch (Exception ex) { BossRulesLogger.LogError((object)("Failed to read " + AltarYamlFilePath + ". " + ex.GetType().Name + ": " + ex.Message)); return; } if (ApplyAltarYaml(text, source) && IsSourceOfTruth) { _syncedAltarYaml.AssignLocalValue(text); } } private void LoadLocalRulesYamlAndPublish(string source) { BossRuleConfigurationFiles.EnsureDefaultFile(); string text; try { text = File.ReadAllText(RulesYamlFilePath); } catch (Exception ex) { BossRulesLogger.LogError((object)("Failed to read " + RulesYamlFilePath + ". " + ex.GetType().Name + ": " + ex.Message)); return; } if (ApplyRulesYaml(text, source) && IsSourceOfTruth) { _syncedRulesYaml.AssignLocalValue(text); } } private void LoadLocalForsakenPowersYamlAndPublish(string source) { ForsakenPowerConfigurationFiles.EnsureDefaultFile(); string text; try { text = File.ReadAllText(ForsakenPowersYamlFilePath); } catch (Exception ex) { BossRulesLogger.LogError((object)("Failed to read " + ForsakenPowersYamlFilePath + ". " + ex.GetType().Name + ": " + ex.Message)); return; } if (ApplyForsakenPowersYaml(text, source) && IsSourceOfTruth) { _syncedForsakenPowersYaml.AssignLocalValue(text); } } private bool ApplyAltarYaml(string yaml, string source) { string text = yaml ?? ""; BossRulesDebugLog.Client($"Applying altar YAML source={source} bytes={text.Length}."); if (!AltarConfiguration.TryParse(text, source, out IReadOnlyList<AltarConfigurationEntry> entries)) { return false; } BossRulesDebugLog.Client($"Parsed altar YAML source={source} entries={entries.Count}."); AltarRuntime.Reload(entries); return true; } private bool ApplyRulesYaml(string yaml, string source) { if (!BossRuleConfiguration.TryParse(yaml, source, out BossRuleConfigurationState state)) { return false; } BossRulesRuntime.Reload(state); return true; } private bool ApplyForsakenPowersYaml(string yaml, string source) { if (!ForsakenPowerConfiguration.TryParse(yaml, source, out IReadOnlyList<ForsakenPowerDefinition> entries)) { return false; } ForsakenPowerRuntime.Configure(entries); return true; } } internal sealed class AltarConfigurationEntry { [YamlMember(Order = 1)] public string Prefab { get; set; } = ""; [YamlMember(Order = 2)] public bool Enabled { get; set; } = true; [YamlMember(Order = 3)] public AltarOfferingBowlDefinition? OfferingBowl { get; set; } [YamlMember(Order = 4)] public List<AltarItemStandDefinition>? ItemStands { get; set; } } internal sealed class AltarOfferingBowlDefinition { [YamlMember(Order = 1)] public string? BossItem { get; set; } [YamlMember(Order = 2)] public int? BossItems { get; set; } [YamlMember(Order = 3)] public string? BossPrefab { get; set; } [YamlMember(Order = 4)] public string? ItemPrefab { get; set; } [YamlMember(Order = 5)] public string? SetGlobalKey { get; set; } [YamlMember(Order = 6)] public bool? RenderSpawnAreaGizmos { get; set; } [YamlMember(Order = 7)] public bool? AlertOnSpawn { get; set; } [YamlMember(Order = 8)] public float? SpawnBossDelay { get; set; } [YamlMember(Order = 9)] public FloatRangeDefinition? SpawnBossDistance { get; set; } [YamlMember(Order = 10)] public float? SpawnBossMaxYDistance { get; set; } [YamlMember(Order = 11)] public int? GetSolidHeightMargin { get; set; } [YamlMember(Order = 12)] public bool? EnableSolidHeightCheck { get; set; } [YamlMember(Order = 13)] public float? SpawnPointClearingRadius { get; set; } [YamlMember(Order = 14)] public float? SpawnYOffset { get; set; } [YamlMember(Order = 15)] public bool? UseItemStands { get; set; } [YamlMember(Order = 16)] public string? ItemStandPrefix { get; set; } [YamlMember(Order = 17)] public float? ItemStandMaxRange { get; set; } [YamlMember(Order = 18)] public float? RespawnMinutes { get; set; } } internal sealed class AltarItemStandDefinition { [YamlMember(Order = 1)] public string? Path { get; set; } [YamlMember(Order = 2)] public bool? CanBeRemoved { get; set; } [YamlMember(Order = 3)] public bool? AutoAttach { get; set; } [YamlMember(Order = 4)] public string? OrientationType { get; set; } [YamlMember(Order = 5)] public List<string>? SupportedTypes { get; set; } [YamlMember(Order = 6)] public List<string>? SupportedItems { get; set; } [YamlMember(Order = 7)] public List<string>? UnsupportedItems { get; set; } [YamlMember(Order = 8)] public float? PowerActivationDelay { get; set; } [YamlMember(Order = 9)] public string? GuardianPower { get; set; } } internal static class AltarConfiguration { private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); internal static bool TryParse(string yaml, string source, out IReadOnlyList<AltarConfigurationEntry> entries) { entries = Array.Empty<AltarConfigurationEntry>(); try { List<AltarConfigurationEntry> list = (string.IsNullOrWhiteSpace(yaml) ? new List<AltarConfigurationEntry>() : Deserializer.Deserialize<List<AltarConfigurationEntry>>(yaml)); entries = Normalize(list ?? new List<AltarConfigurationEntry>()); BossRulesPlugin.BossRulesLogger.LogInfo((object)$"Loaded altar YAML from {source}: {entries.Count} entries."); return true; } catch (Exception ex) { BossRulesPlugin.BossRulesLogger.LogError((object)("Rejected altar YAML from " + source + ". Keeping the previous configuration. " + ex.GetType().Name + ": " + ex.Message)); return false; } } private static IReadOnlyList<AltarConfigurationEntry> Normalize(List<AltarConfigurationEntry> entries) { foreach (AltarConfigurationEntry entry in entries) { entry.Prefab = (entry.Prefab ?? "").Trim(); NormalizeOfferingBowl(entry.OfferingBowl); NormalizeItemStands(entry.ItemStands); } return entries.Where((AltarConfigurationEntry entry) => entry.Prefab.Length > 0).ToList(); } private static void NormalizeOfferingBowl(AltarOfferingBowlDefinition? definition) { if (definition != null) { definition.BossItem = NormalizeOptionalString(definition.BossItem); definition.BossPrefab = NormalizeOptionalString(definition.BossPrefab); definition.ItemPrefab = NormalizeOptionalString(definition.ItemPrefab); definition.SetGlobalKey = NormalizeOptionalString(definition.SetGlobalKey); definition.ItemStandPrefix = NormalizeOptionalString(definition.ItemStandPrefix); } } private static void NormalizeItemStands(List<AltarItemStandDefinition>? definitions) { if (definitions == null) { return; } foreach (AltarItemStandDefinition definition in definitions) { definition.Path = NormalizeOptionalString(definition.Path); definition.OrientationType = NormalizeOptionalString(definition.OrientationType); definition.SupportedTypes = NormalizeStringList(definition.SupportedTypes); definition.SupportedItems = NormalizeStringList(definition.SupportedItems); definition.UnsupportedItems = NormalizeStringList(definition.UnsupportedItems); definition.GuardianPower = NormalizeOptionalString(definition.GuardianPower); } } private static List<string>? NormalizeStringList(List<string>? values) { List<string> list = (from value in values?.Select((string value) => (value ?? "").Trim()) where value.Length > 0 select value).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); if (list == null || list.Count <= 0) { return null; } return list; } private static string? NormalizeOptionalString(string? value) { string text = (value ?? "").Trim(); if (text.Length != 0) { return text; } return null; } } internal static class AltarConfigurationFiles { internal static void EnsureDefaultFiles() { EnsureTextFile(BossRulesPlugin.AltarYamlFilePath, BuildDefaultAltarYaml()); EnsureTextFile(BossRulesPlugin.AltarReferenceYamlFilePath, BuildReferencePlaceholderYaml()); } private static void EnsureTextFile(string path, string content) { if (!File.Exists(path)) { File.WriteAllText(path, content); BossRulesPlugin.BossRulesLogger.LogInfo((object)("Created " + path + ".")); } } private static string BuildDefaultAltarYaml() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# BossRules altar overrides"); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# This file owns boss altar OfferingBowl and boss ItemStand edits."); stringBuilder.AppendLine("# It is intentionally inert by default."); stringBuilder.AppendLine("# Copy rows from BossRules.altar.reference.yml and uncomment only the fields you want to override."); stringBuilder.AppendLine("# Unless noted otherwise, null/empty/omitted override fields keep the current prefab value."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# offeringBowl"); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# - prefab: Bonemass"); stringBuilder.AppendLine("# enabled: true # Default true when omitted."); stringBuilder.AppendLine("# offeringBowl:"); stringBuilder.AppendLine("# bossItem: null # ex) WitheredBone. Required direct offering item prefab."); stringBuilder.AppendLine("# bossItems: null # ex) 10. Number of bossItem items required; clamped to at least 1 when set."); stringBuilder.AppendLine("# bossPrefab: null # ex) Bonemass. Boss character prefab spawned after a valid offering."); stringBuilder.AppendLine("# itemPrefab: null # ex) Wishbone. Optional item reward prefab instead of spawning a boss."); stringBuilder.AppendLine("# setGlobalKey: null # ex) defeated_bonemass. Optional global key set after a valid offering."); stringBuilder.AppendLine("# renderSpawnAreaGizmos: null # ex) false. True draws the boss spawn search area while selected."); stringBuilder.AppendLine("# alertOnSpawn: null # ex) false. True calls BaseAI.Alert() on the spawned boss."); stringBuilder.AppendLine("# spawnBossDelay: null # ex) 5. Seconds to wait before spawning; clamped to at least 0."); stringBuilder.AppendLine("# spawnBossDistance: null # ex) 0~40 or {min: 0, max: 40}. Each side can be overridden separately."); stringBuilder.AppendLine("# spawnBossMaxYDistance: null # ex) 9999. Vertical spawn search distance; clamped to at least 0."); stringBuilder.AppendLine("# getSolidHeightMargin: null # ex) 1000. Terrain raycast margin; clamped to at least 0."); stringBuilder.AppendLine("# enableSolidHeightCheck: null # ex) true. True requires valid ground height."); stringBuilder.AppendLine("# spawnPointClearingRadius: null # ex) 0. Clearing radius before boss spawn; clamped to at least 0."); stringBuilder.AppendLine("# spawnYOffset: null # ex) 1. Vertical offset added to the chosen spawn position."); stringBuilder.AppendLine("# useItemStands: null # ex) true. True uses nearby ItemStands instead of direct UseItem offerings."); stringBuilder.AppendLine("# itemStandPrefix: null # ex) Boss. Object-name prefix used to select nearby ItemStands."); stringBuilder.AppendLine("# itemStandMaxRange: null # ex) 20. Max scan distance for nearby ItemStands; clamped to at least 0."); stringBuilder.AppendLine("# respawnMinutes: null # Null/omitted becomes 0, disabling BossRules altar cooldown. Set >0 for cooldown minutes."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# itemStands"); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# - prefab: StartTemple"); stringBuilder.AppendLine("# enabled: true # Default true when omitted."); stringBuilder.AppendLine("# itemStands:"); stringBuilder.AppendLine("# - path: null # Null/empty/omitted targets all relevant stands. ex) BossStone_Eikthyr[0]/itemstand[0] targets one reference path."); stringBuilder.AppendLine("# canBeRemoved: null # ex) true. True allows players to remove the attached item."); stringBuilder.AppendLine("# autoAttach: null # ex) false. True automatically attaches compatible dropped items."); stringBuilder.AppendLine("# orientationType: null # ex) Vertical. ItemStand.Orientation name."); stringBuilder.AppendLine("# supportedTypes: [] # ex) [OneHandedWeapon, TwoHandedWeapon]. ItemDrop.ItemType names."); stringBuilder.AppendLine("# supportedItems: [] # ex) [TrophyDeer]. Explicitly allowed item prefabs."); stringBuilder.AppendLine("# unsupportedItems: [] # ex) [TrophyDeer]. Explicitly blocked item prefabs."); stringBuilder.AppendLine("# powerActivationDelay: null # ex) 2. Seconds before guardianPower activates; clamped to at least 0."); stringBuilder.AppendLine("# guardianPower: null # ex) GP_Eikthyr. StatusEffect prefab granted when used."); return stringBuilder.ToString(); } private static string BuildReferencePlaceholderYaml() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# BossRules altar reference"); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# This file is generated automatically after ZoneSystem location prefabs load."); stringBuilder.AppendLine("[]"); return stringBuilder.ToString(); } } internal sealed class AltarReferenceEntry { [YamlMember(Order = 1)] public string Prefab { get; set; } = ""; [YamlIgnore] public string PrefabAssetId { get; set; } = ""; [YamlIgnore] public string SourcePrefabName { get; set; } = ""; [YamlIgnore] public string OwnerName { get; set; } = "Unknown / Untracked"; [YamlMember(Order = 2)] public AltarOfferingBowlDefinition? OfferingBowl { get; set; } [YamlMember(Order = 3)] public List<AltarReferenceItemStandDefinition>? ItemStands { get; set; } } internal sealed class AltarReferenceItemStandDefinition { [YamlMember(Order = 1)] public string? Path { get; set; } [YamlMember(Order = 2)] public bool? CanBeRemoved { get; set; } [YamlMember(Order = 3)] public bool? AutoAttach { get; set; } [YamlMember(Order = 4)] public string? OrientationType { get; set; } [YamlMember(Order = 5)] public FlowStringListDefinition? SupportedTypes { get; set; } [YamlMember(Order = 6)] public FlowStringListDefinition? SupportedItems { get; set; } [YamlMember(Order = 7)] public FlowStringListDefinition? UnsupportedItems { get; set; } [YamlMember(Order = 8)] public float? PowerActivationDelay { get; set; } [YamlMember(Order = 9)] public string? GuardianPower { get; set; } } internal sealed class FlowStringListDefinition : IYamlConvertible { public List<string> Values { get; set; } = new List<string>(); public FlowStringListDefinition() { } public FlowStringListDefinition(IEnumerable<string> values) { Values = (from value in values where !string.IsNullOrWhiteSpace(value) select value.Trim()).ToList(); } void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer) { Values.Clear(); parser.Consume<SequenceStart>(); SequenceEnd @event; while (!parser.Accept<SequenceEnd>(out @event)) { Values.Add((parser.Consume<YamlDotNet.Core.Events.Scalar>().Value ?? "").Trim()); } parser.Consume<SequenceEnd>(); } void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer) { emitter.Emit(new SequenceStart(null, null, isImplicit: true, SequenceStyle.Flow)); foreach (string value in Values) { emitter.Emit(new YamlDotNet.Core.Events.Scalar(value)); } emitter.Emit(new SequenceEnd()); } } internal static class AltarReferenceGenerator { private static readonly ISerializer Serializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull | DefaultValuesHandling.OmitDefaults).Build(); private const float AutoRefreshIdleRetryDelaySeconds = 1f; private const float AutoRefreshRetryDelaySeconds = 5f; private static readonly HashSet<string> DuplicateComponentWarnings = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static bool _autoRefreshDone; private static AltarOfferingBowlDefinition? _queenDungeonReferenceDefinition; private static bool _queenDungeonReferenceCaptureAttempted; private static bool _queenDungeonReferenceCaptureFailed; private static bool _queenDungeonReferenceWarningLogged; private static bool _hasSeenZoneSystem; private static float _nextAutoRefreshAttemptAt; internal static void ResetAutoRefresh() { ResetAutoRefreshState(); AltarPrefabOwnerResolver.ResetRuntimeSnapshot(allowCacheLoad: false); } internal static void ResetForZoneSystem() { ResetAutoRefreshState(); AltarPrefabOwnerResolver.ResetRuntimeSnapshot(!_hasSeenZoneSystem); _hasSeenZoneSystem = true; } internal static void ShutdownAutoRefresh() { ResetAutoRefreshState(); AltarPrefabOwnerResolver.ResetRuntimeSnapshot(allowCacheLoad: false); _hasSeenZoneSystem = false; } private static void ResetAutoRefreshState() { _autoRefreshDone = false; _queenDungeonReferenceDefinition = null; _queenDungeonReferenceCaptureAttempted = false; _queenDungeonReferenceCaptureFailed = false; _queenDungeonReferenceWarningLogged = false; _nextAutoRefreshAttemptAt = 0f; } internal static void TryAutoRefreshReferenceConfigurationFile() { if (_autoRefreshDone || !BossRulesPlugin.IsSourceOfTruth) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextAutoRefreshAttemptAt) { return; } if ((Object)(object)ZoneSystem.instance == (Object)null) { _nextAutoRefreshAttemptAt = realtimeSinceStartup + 1f; return; } if (ZoneSystem.instance.m_locations == null || ZoneSystem.instance.m_locations.Count == 0) { _nextAutoRefreshAttemptAt = realtimeSinceStartup + 1f; return; } if (!IsQueenDungeonReferenceDataReady()) { _nextAutoRefreshAttemptAt = realtimeSinceStartup + 1f; return; } try { int entryCount; string content = BuildReferenceConfigurationContent(out entryCount); if (_queenDungeonReferenceCaptureFailed) { _nextAutoRefreshAttemptAt = realtimeSinceStartup + 5f; return; } if (entryCount == 0) { _nextAutoRefreshAttemptAt = realtimeSinceStartup + 1f; return; } WriteReferenceConfigurationFile(content); _autoRefreshDone = true; } catch (Exception exception) { _nextAutoRefreshAttemptAt = Time.realtimeSinceStartup + 5f; BossRulesPlugin.BossRulesLogger.LogWarning((object)$"Failed to update altar reference configuration at {BossRulesPlugin.AltarReferenceYamlFilePath}. {FormatException(exception)} Retrying in {5f:0.#}s."); } } private static string FormatException(Exception exception) { Exception ex = exception; while (ex is TargetInvocationException && ex.InnerException != null) { ex = ex.InnerException; } if (ex != exception) { return exception.GetType().Name + ": " + exception.Message + " Inner " + ex.GetType().Name + ": " + ex.Message + "."; } return exception.GetType().Name + ": " + exception.Message + "."; } private static string BuildReferenceConfigurationContent(out int entryCount) { _queenDungeonReferenceDefinition = null; _queenDungeonReferenceCaptureAttempted = false; _queenDungeonReferenceCaptureFailed = false; List<AltarReferenceEntry> list = CaptureReferenceEntries().ToList(); entryCount = list.Count; StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# BossRules altar reference"); stringBuilder.AppendLine("# Generated from loaded ZoneSystem location prefabs and supported nested dungeon rooms."); stringBuilder.AppendLine("# Copy rows to BossRules.altar.yml to override."); stringBuilder.AppendLine("# This file is overwritten automatically."); stringBuilder.Append(SerializeReferenceEntries(list)); return stringBuilder.ToString(); } private static string SerializeReferenceEntries(IReadOnlyList<AltarReferenceEntry> entries) { if (entries.Count == 0) { return "[]" + Environment.NewLine; } StringBuilder stringBuilder = new StringBuilder(); bool flag = false; foreach (IGrouping<string, AltarReferenceEntry> item in entries.OrderBy((AltarReferenceEntry entry) => AltarPrefabOwnerResolver.GetOwnerSortBucket(AltarPrefabOwnerResolver.NormalizeOwnerName(entry.OwnerName))).ThenBy<AltarReferenceEntry, string>((AltarReferenceEntry entry) => AltarPrefabOwnerResolver.NormalizeOwnerName(entry.OwnerName), StringComparer.OrdinalIgnoreCase).ThenBy<AltarReferenceEntry, string>((AltarReferenceEntry entry) => entry.Prefab, StringComparer.OrdinalIgnoreCase) .GroupBy<AltarReferenceEntry, string>((AltarReferenceEntry entry) => AltarPrefabOwnerResolver.NormalizeOwnerName(entry.OwnerName), StringComparer.OrdinalIgnoreCase)) { if (flag) { stringBuilder.AppendLine(); } stringBuilder.Append("# ===== ").Append(item.Key).AppendLine(" ====="); foreach (AltarReferenceEntry item2 in item) { stringBuilder.AppendLine(SerializeReferenceEntry(item2)); } flag = true; } return stringBuilder.ToString(); } private static string SerializeReferenceEntry(AltarReferenceEntry entry) { string text = Serializer.Serialize(new AltarReferenceEntry[1] { entry }).TrimEnd('\r', '\n'); if (!RequiresQuotedPrefabScalar(entry.Prefab)) { return text; } int num = text.IndexOfAny(new char[2] { '\r', '\n' }); string text2 = ((num >= 0) ? text.Substring(num) : ""); string text3 = EscapeDoubleQuotedYamlScalar(entry.Prefab); return "- prefab: \"" + text3 + "\"" + text2; } private static bool RequiresQuotedPrefabScalar(string prefab) { return (prefab ?? "").IndexOf(':') >= 0; } private static string EscapeDoubleQuotedYamlScalar(string value) { return (value ?? "").Replace("\\", "\\\\").Replace("\"", "\\\""); } private static List<AltarReferenceEntry> CaptureReferenceEntries() { List<AltarReferenceEntry> list = new List<AltarReferenceEntry>(); List<ZoneLocation> list2 = new List<ZoneLocation>(); HashSet<string> capturedPrefabs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); DuplicateComponentWarnings.Clear(); try { foreach (ZoneLocation location in ZoneSystem.instance.m_locations) { if (TryCaptureReferenceEntry(location, capturedPrefabs, list2, out AltarReferenceEntry entry) && entry != null) { list.Add(entry); } } AltarPrefabOwnerSnapshot snapshot = AltarPrefabOwnerResolver.GetSnapshot(list.Select((AltarReferenceEntry altarReferenceEntry) => new AltarPrefabOwnerSource(altarReferenceEntry.Prefab, altarReferenceEntry.PrefabAssetId, altarReferenceEntry.SourcePrefabName))); foreach (AltarReferenceEntry item in list) { item.OwnerName = snapshot.GetOwnerName(item.Prefab); } return list; } finally { Exception ex = null; for (int num = list2.Count - 1; num >= 0; num--) { try { list2[num].m_prefab.Release(); } catch (Exception ex2) { if (ex == null) { ex = ex2; } } } if (ex != null) { BossRulesPlugin.BossRulesLogger.LogWarning((object)("Failed to release one or more altar reference location prefabs. " + FormatException(ex))); } } } private static bool TryCaptureReferenceEntry(ZoneLocation location, HashSet<string> capturedPrefabs, ICollection<ZoneLocation> loadedLocationPrefabs, out AltarReferenceEntry? entry) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) entry = null; if (location == null || !location.m_prefab.IsValid) { return false; } string zoneLocationPrefabName = AltarLocationResolver.GetZoneLocationPrefabName(location); if (zoneLocationPrefabName.Length == 0 || !capturedPrefabs.Add(zoneLocationPrefabName)) { return false; } location.m_prefab.Load(); loadedLocationPrefabs.Add(location); return TryCaptureLoadedReferenceEntry(location, zoneLocationPrefabName, out entry); } private static bool TryCaptureLoadedReferenceEntry(ZoneLocation location, string prefabName, out AltarReferenceEntry? entry) { entry = null; GameObject rootPrefab = location.m_prefab.Asset; if ((Object)(object)rootPrefab == (Object)null) { return false; } OfferingBowl[] componentsInChildren = rootPrefab.GetComponentsInChildren<OfferingBowl>(true); ItemStand[] componentsInChildren2 = rootPrefab.GetComponentsInChildren<ItemStand>(true); WarnDuplicateComponent(prefabName, "OfferingBowl", componentsInChildren.Length); AltarOfferingBowlDefinition altarOfferingBowlDefinition = ((componentsInChildren.Length != 0) ? ConvertReferenceOfferingBowl(AltarRuntime.CaptureOfferingBowlSnapshot(componentsInChildren[0])) : null); if (altarOfferingBowlDefinition == null && QueenDungeonAltarSupport.IsSupportedLocationPrefab(prefabName)) { altarOfferingBowlDefinition = TryCaptureQueenDungeonOfferingBowl(); } if (altarOfferingBowlDefinition == null && componentsInChildren2.Length == 0) { return false; } entry = new AltarReferenceEntry { Prefab = prefabName, PrefabAssetId = (((AssetID)(ref location.m_prefab.m_assetID)).IsValid ? ((object)Unsafe.As<AssetID, AssetID>(ref location.m_prefab.m_assetID)/*cast due to .constrained prefix*/).ToString() : ""), SourcePrefabName = (((Object)rootPrefab).name ?? ""), OfferingBowl = altarOfferingBowlDefinition, ItemStands = ((componentsInChildren2.Length != 0) ? (from itemStand in componentsInChildren2 where (Object)(object)itemStand != (Object)null select ConvertReferenceItemStand(rootPrefab.transform, itemStand)).OrderBy<AltarReferenceItemStandDefinition, string>((AltarReferenceItemStandDefinition itemStand) => itemStand.Path, StringComparer.Ordinal).ToList() : null) }; if (entry.OfferingBowl == null) { List<AltarReferenceItemStandDefinition> itemStands = entry.ItemStands; if (itemStands != null) { return itemStands.Count > 0; } return false; } return true; } private static bool IsQueenDungeonReferenceDataReady() { if (!ZoneSystem.instance.m_locations.Any((ZoneLocation location) => QueenDungeonAltarSupport.IsSupportedLocationPrefab(AltarLocationResolver.GetZoneLocationPrefabName(location)))) { return true; } if ((Object)(object)DungeonDB.instance == (Object)null || DungeonDB.GetRooms().Count == 0) { return false; } bool num = DungeonDB.GetRooms().Any(QueenDungeonAltarSupport.IsTargetRoom); if (!num) { WarnQueenDungeonReference("Dungeon room 'dvergr_new_bossroom_ENTRANCE02' was not registered. Waiting to capture the 'Mistlands_DvergrBossEntrance1' altar reference."); } return num; } private static AltarOfferingBowlDefinition? TryCaptureQueenDungeonOfferingBowl() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (_queenDungeonReferenceCaptureAttempted) { return _queenDungeonReferenceDefinition; } _queenDungeonReferenceCaptureAttempted = true; RoomData val = ((IEnumerable<RoomData>)DungeonDB.GetRooms()).FirstOrDefault((Func<RoomData, bool>)QueenDungeonAltarSupport.IsTargetRoom); if (val == null) { MarkQueenDungeonReferenceCaptureFailed("Dungeon room 'dvergr_new_bossroom_ENTRANCE02' was not registered. The 'Mistlands_DvergrBossEntrance1' altar reference could not be captured."); return null; } try { val.m_prefab.Load(); GameObject asset = val.m_prefab.Asset; if ((Object)(object)asset == (Object)null) { MarkQueenDungeonReferenceCaptureFailed("Dungeon room 'dvergr_new_bossroom_ENTRANCE02' could not be loaded. The 'Mistlands_DvergrBossEntrance1' altar reference could not be captured."); return null; } OfferingBowl[] array = asset.GetComponentsInChildren<OfferingBowl>(true).Where(QueenDungeonAltarSupport.IsTargetOfferingBowl).ToArray(); if (array.Length != 1) { MarkQueenDungeonReferenceCaptureFailed("Dungeon room 'dvergr_new_bossroom_ENTRANCE02' contains " + string.Format("{0} matching '{1}' components; expected exactly one. ", array.Length, "offeraltar_queen") + "The 'Mistlands_DvergrBossEntrance1' altar reference could not be captured."); return null; } _queenDungeonReferenceDefinition = ConvertReferenceOfferingBowl(AltarRuntime.CaptureOfferingBowlSnapshot(array[0])); return _queenDungeonReferenceDefinition; } catch (Exception exception) { MarkQueenDungeonReferenceCaptureFailed("Dungeon room 'dvergr_new_bossroom_ENTRANCE02' failed to load. " + FormatException(exception) + " The 'Mistlands_DvergrBossEntrance1' altar reference will be retried."); return null; } finally { val.m_prefab.Release(); } } private static void MarkQueenDungeonReferenceCaptureFailed(string message) { _queenDungeonReferenceCaptureFailed = true; WarnQueenDungeonReference(message); } private static void WarnQueenDungeonReference(string message) { if (!_queenDungeonReferenceWarningLogged) { _queenDungeonReferenceWarningLogged = true; BossRulesPlugin.BossRulesLogger.LogWarning((object)message); } } private static AltarOfferingBowlDefinition ConvertReferenceOfferingBowl(OfferingBowlSnapshot snapshot) { return new AltarOfferingBowlDefinition { BossItem = ((snapshot.BossItem.Length == 0) ? null : snapshot.BossItem), BossItems = ((snapshot.BossItems == 1) ? ((int?)null) : new int?(snapshot.BossItems)), BossPrefab = ((snapshot.BossPrefab.Length == 0) ? null : snapshot.BossPrefab), ItemPrefab = ((snapshot.ItemPrefab.Length == 0) ? null : snapshot.ItemPrefab), SetGlobalKey = (string.IsNullOrWhiteSpace(snapshot.SetGlobalKey) ? null : snapshot.SetGlobalKey), RenderSpawnAreaGizmos = (snapshot.RenderSpawnAreaGizmos ? new bool?(true) : ((bool?)null)), AlertOnSpawn = (snapshot.AlertOnSpawn ? new bool?(true) : ((bool?)null)), SpawnBossDelay = (IsReferenceDefault(snapshot.SpawnBossDelay, 5f) ? ((float?)null) : new float?(snapshot.SpawnBossDelay)), SpawnBossDistance = RangeFormatting.FromReference(snapshot.SpawnBossMinDistance, snapshot.SpawnBossMaxDistance, 0f, 40f), SpawnBossMaxYDistance = (IsReferenceDefault(snapshot.SpawnBossMaxYDistance, 9999f) ? ((float?)null) : new float?(snapshot.SpawnBossMaxYDistance)), GetSolidHeightMargin = ((snapshot.GetSolidHeightMargin == 1000) ? ((int?)null) : new int?(snapshot.GetSolidHeightMargin)), EnableSolidHeightCheck = (snapshot.EnableSolidHeightCheck ? ((bool?)null) : new bool?(false)), SpawnPointClearingRadius = (IsReferenceDefault(snapshot.SpawnPointClearingRadius, 0f) ? ((float?)null) : new float?(snapshot.SpawnPointClearingRadius)), SpawnYOffset = (IsReferenceDefault(snapshot.SpawnYOffset, 1f) ? ((float?)null) : new float?(snapshot.SpawnYOffset)), UseItemStands = (snapshot.UseItemStands ? new bool?(true) : ((bool?)null)), ItemStandPrefix = (string.IsNullOrWhiteSpace(snapshot.ItemStandPrefix) ? null : snapshot.ItemStandPrefix), ItemStandMaxRange = (IsReferenceDefault(snapshot.ItemStandMaxRange, 20f) ? ((float?)null) : new float?(snapshot.ItemStandMaxRange)), RespawnMinutes = null }; } private static AltarReferenceItemStandDefinition ConvertReferenceItemStand(Transform root, ItemStand itemStand) { //IL_0071: Unknown result type (might be due to invalid IL or missing references) ItemStandSnapshot itemStandSnapshot = AltarRuntime.CaptureItemStandSnapshot(itemStand); return new AltarReferenceItemStandDefinition { Path = AltarRuntime.GetRelativePath(root, ((Component)itemStand).transform), CanBeRemoved = (itemStandSnapshot.CanBeRemoved ? ((bool?)null) : new bool?(false)), AutoAttach = (itemStandSnapshot.AutoAttach ? new bool?(true) : ((bool?)null)), OrientationType = ((string.IsNullOrWhiteSpace(itemStandSnapshot.OrientationType) || itemStandSnapshot.OrientationType == ((object)(Orientation)1/*cast due to .constrained prefix*/).ToString()) ? null : itemStandSnapshot.OrientationType), SupportedTypes = ((itemStandSnapshot.SupportedTypes.Count == 0) ? null : new FlowStringListDefinition(itemStandSnapshot.SupportedTypes)), SupportedItems = ((itemStandSnapshot.SupportedItems.Count == 0) ? null : new FlowStringListDefinition(itemStandSnapshot.SupportedItems)), UnsupportedItems = ((itemStandSnapshot.UnsupportedItems.Count == 0) ? null : new FlowStringListDefinition(itemStandSnapshot.UnsupportedItems)), PowerActivationDelay = (IsReferenceDefault(itemStandSnapshot.PowerActivationDelay, 2f) ? ((float?)null) : new float?(itemStandSnapshot.PowerActivationDelay)), GuardianPower = (string.IsNullOrWhiteSpace(itemStandSnapshot.GuardianPower) ? null : itemStandSnapshot.GuardianPower) }; } private static bool IsReferenceDefault(float actual, float expected) { return Math.Abs(actual - expected) < 0.0001f; } private static void WarnDuplicateComponent(string prefabName, string componentName, int count) { if (count > 1) { string item = prefabName + "@" + componentName; if (DuplicateComponentWarnings.Add(item)) { BossRulesPlugin.BossRulesLogger.LogWarning((object)("Location prefab '" + prefabName + "' has multiple " + componentName + " components. The first one will be used for BossRules.altar.yml.")); } } } private static void WriteReferenceConfigurationFile(string content) { string altarReferenceYamlFilePath = BossRulesPlugin.AltarReferenceYamlFilePath; if (!string.Equals(File.Exists(altarReferenceYamlFilePath) ? File.ReadAllText(altarReferenceYamlFilePath) : "", content, StringComparison.Ordinal)) { File.WriteAllText(altarReferenceYamlFilePath, content, Encoding.UTF8); BossRulesPlugin.BossRulesLogger.LogInfo((object)("Updated altar reference configuration at " + altarReferenceYamlFilePath + ".")); } } } internal readonly struct AltarPrefabOwnerSource { internal string PrefabName { get; } internal string PrefabAssetId { get; } internal string SourcePrefabName { get; } internal AltarPrefabOwnerSource(string prefabName, string prefabAssetId, string sourcePrefabName) { PrefabName = (prefabName ?? "").Trim(); PrefabAssetId = NormalizeAssetId(prefabAssetId); SourcePrefabName = (sourcePrefabName ?? "").Trim(); } private static string NormalizeAssetId(string? assetId) { return (assetId ?? "").Trim().ToLowerInvariant(); } } internal sealed class AltarPrefabOwnerSnapshot { private readonly Dictionary<string, string> _owners; internal AltarPrefabOwnerSnapshot(Dictionary<string, string> owners) { _owners = owners ?? new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); } internal string GetOwnerName(string? prefabName) { string text = (prefabName ?? "").Trim(); if (text.Length <= 0 || !_owners.TryGetValue(text, out string value) || string.IsNullOrWhiteSpace(value)) { return "Unknown / Untracked"; } return value; } } internal static class AltarPrefabOwnerResolver { private enum VanillaCatalogState { Uninitialized, Loaded, Unavailable } private readonly struct OwnerMapping { internal string OwnerName { get; } internal int Priority { get; } internal OwnerMapping(string ownerName, int priority) { OwnerName = ownerName; Priority = priority; } } private sealed class PluginResources { internal string OwnerName { get; set; } = ""; internal string[] ResourceNames { get; set; } = Array.Empty<string>(); } internal const string VanillaOwnerName = "Valheim"; internal const string UnknownOwnerName = "Unknown / Untracked"; private const string CacheFormatVersion = "v1"; private const string ResolverLogicVersion = "location-owner-v4"; private const string RuntimeAssetIdPrefix = "000000010000000100000001"; private const string JotunnZoneManagerTypeName = "Jotunn.Managers.ZoneManager"; private const string JotunnPrefabManagerTypeName = "Jotunn.Managers.PrefabManager"; private const string LocationManagerTemplateTypeName = "LocationManager.Location"; private static readonly object Sync = new object(); private static readonly HashSet<string> VanillaPrefabNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> VanillaAssetIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static string _snapshotSignature = ""; private static AltarPrefabOwnerSnapshot _snapshot = new AltarPrefabOwnerSnapshot(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)); private static VanillaCatalogState _vanillaCatalogState; private static bool _resolverWarningLogged; private static bool _cacheWarningLogged; private static bool _vanillaCatalogWarningLogged; private static bool _allowCacheLoad = true; internal static AltarPrefabOwnerSnapshot GetSnapshot(IEnumerable<AltarPrefabOwnerSource> prefabSources) { List<AltarPrefabOwnerSource> list = (from @group in (prefabSources ?? Enumerable.Empty<AltarPrefabOwnerSource>()).Where((AltarPrefabOwnerSource source) => source.PrefabName.Length > 0).GroupBy<AltarPrefabOwnerSource, string>((AltarPrefabOwnerSource source) => source.PrefabName, StringComparer.OrdinalIgnoreCase) select @group.First()).OrderBy<AltarPrefabOwnerSource, string>((AltarPrefabOwnerSource source) => source.PrefabName, StringComparer.OrdinalIgnoreCase).ToList(); if (list.Count == 0) { return new AltarPrefabOwnerSnapshot(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)); } try { string text = BuildSnapshotSignature(list); if (string.Equals(text, _snapshotSignature, StringComparison.Ordinal)) { return _snapshot; } lock (Sync) { if (string.Equals(text, _snapshotSignature, StringComparison.Ordinal)) { return _snapshot; } if (!_allowCacheLoad || !TryLoadSnapshotFromCache(text, list.Select((AltarPrefabOwnerSource source) => source.PrefabName).ToList(), out Dictionary<string, string> owners)) { owners = BuildOwnerMappings(list); SaveSnapshotToCache(text, owners); } _snapshot = new AltarPrefabOwnerSnapshot(owners); _snapshotSignature = text; _allowCacheLoad = true; return _snapshot; } } catch (Exception exception) { WarnResolverFailure(exception); return BuildUnknownSnapshot(list.Select((AltarPrefabOwnerSource source) => source.PrefabName)); } } internal static void ResetRuntimeSnapshot(bool allowCacheLoad) { lock (Sync) { _snapshotSignature = ""; _snapshot = new AltarPrefabOwnerSnapshot(new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)); _allowCacheLoad = allowCacheLoad; } } internal static int GetOwnerSortBucket(string ownerName) { if (string.Equals(ownerName, "Valheim", StringComparison.OrdinalIgnoreCase)) { return 0; } if (!string.Equals(ownerName, "Unknown / Untracked", StringComparison.OrdinalIgnoreCase)) { return 1; } return 2; } internal static string NormalizeOwnerName(string? ownerName) { string text = (ownerName ?? "").Replace('\r', ' ').Replace('\n', ' ').Trim(); if (text.Length <= 0) { return "Unknown / Untracked"; } return text; } private static Dictionary<string, string> BuildOwnerMappings(IReadOnlyCollection<AltarPrefabOwnerSource> prefabSources) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (AltarPrefabOwnerSource prefabSource in prefabSources) { foreach (string item in EnumerateSourceLookupCandidates(prefabSource)) { hashSet.Add(item); } } Dictionary<string, OwnerMapping> dictionary = new Dictionary<string, OwnerMapping>(StringComparer.OrdinalIgnoreCase); CollectJotunnManagerMappings("Jotunn.Managers.PrefabManager", "Prefabs", 2, hashSet, dictionary); CollectLocationManagerTemplateMappings(hashSet, dictionary); CollectJotunnManagerMappings("Jotunn.Managers.ZoneManager", "Locations", 0, hashSet, dictionary); EnsureVanillaCatalogLoaded(); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (AltarPrefabOwnerSource prefabSource2 in prefabSources) { if (HasProvenance(prefabSource2, dictionary) || IsVanillaSource(prefabSource2)) { continue; } foreach (string item2 in EnumerateSourceLookupCandidates(prefabSource2)) { hashSet2.Add(item2); } } if (hashSet2.Count > 0) { CollectLoadedBundleMappings(hashSet2, dictionary); } Dictionary<string, string> dictionary2 = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (AltarPrefabOwnerSource prefabSource3 in prefabSources) { dictionary2[prefabSource3.PrefabName] = ResolveOwnerName(prefabSource3, dictionary); } return dictionary2; } private static string ResolveOwnerName(AltarPrefabOwnerSource source, IReadOnlyDictionary<string, OwnerMapping> provenance) { foreach (string item in EnumerateSourceLookupCandidates(source)) { if (provenance.TryGetValue(item, out var value) && !string.IsNullOrWhiteSpace(value.OwnerName)) { return NormalizeOwnerName(value.OwnerName); } } if (IsVanillaSource(source)) { return "Valheim"; } return "Unknown / Untracked"; } private static bool HasProvenance(AltarPrefabOwnerSource source, IReadOnlyDictionary<string, OwnerMapping> provenance) { return EnumerateSourceLookupCandidates(source).Any(provenance.ContainsKey); } private static bool IsVanillaSource(AltarPrefabOwnerSource source) { if (_vanillaCatalogState == VanillaCatalogState.Loaded) { if (source.PrefabAssetId.Length > 0 && VanillaAssetIds.Contains(source.PrefabAssetId)) { return true; } if (EnumerateSourceLookupCandidates(source).Any(VanillaPrefabNames.Contains)) { return true; } } return QueenDungeonAltarSupport.IsSupportedLocationPrefab(source.PrefabName); } private static IEnumerable<string> EnumerateSourceLookupCandidates(AltarPrefabOwnerSource source) { HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string item in EnumerateLookupCandidates(source.PrefabName).Concat(EnumerateLookupCandidates(source.SourcePrefabName))) { if (seen.Add(item)) { yield return item; } } } private static IEnumerable<string> EnumerateLookupCandidates(string prefabName) { List<string> candidates = new List<string>(); HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string text = (prefabName ?? "").Trim(); if (text.Length == 0) { yield break; } AddIfNew(text); string text2 = TrimCloneSuffix(text); AddIfNew(text2); int num = text2.IndexOf(':'); if (num > 0) { AddIfNew(text2.Substring(0, num)); } foreach (string item in candidates) { yield return item; } void AddIfNew(string candidate) { string text3 = (candidate ?? "").Trim(); if (text3.Length > 0 && seen.Add(text3)) { candidates.Add(text3); } } } private static void CollectLoadedBundleMappings(HashSet<string> lookupCandidates, Dictionary<string, OwnerMapping> mappings) { List<PluginResources> source = BuildPluginResources(); IEnumerable<AssetBundle> enumerable; try { enumerable = (from bundle in AssetBundle.GetAllLoadedAssetBundles() where (Object)(object)bundle != (Object)null select bundle).OrderBy<AssetBundle, string>((AssetBundle bundle) => ((Object)bundle).name ?? "", StringComparer.OrdinalIgnoreCase).ToArray(); } catch { return; } foreach (AssetBundle item in enumerable) { string bundleName = (((Object)item).name ?? "").Trim(); if (bundleName.Length == 0) { continue; } List<string> list = (from candidate in source where candidate.ResourceNames.Any((string resourceName) => IsBundleResourceMatch(resourceName, bundleName)) select candidate.OwnerName).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); if (list.Count != 1) { continue; } string[] allAssetNames; try { allAssetNames = item.GetAllAssetNames(); } catch { continue; } string[] array = allAssetNames; foreach (string text in array) { if (text.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) { string text2 = Path.GetFileNameWithoutExtension(text).Trim(); if (lookupCandidates.Contains(text2)) { TryAddOwnerMapping(mappings, text2, list[0], 3); } } } } } private static bool IsBundleResourceMatch(string? resourceName, string bundleName) { string text = (resourceName ?? "").Trim(); string text2 = (bundleName ?? "").Trim(); if (text.Length > 0 && text2.Length > 0) { if (!string.Equals(text, text2, StringComparison.OrdinalIgnoreCase)) { return text.EndsWith("." + text2, StringComparison.OrdinalIgnoreCase); } return true; } return false; } private static List<PluginResources> BuildPluginResources() { List<PluginResources> list = new List<PluginResources>(); foreach (PluginInfo value in Chainloader.PluginInfos.Values) { string pluginDisplayName = GetPluginDisplayName(value.Metadata.Name, value.Metadata.GUID); string[] resourceNames; try { resourceNames = ((object)value.Instance)?.GetType().Assembly.GetManifestResourceNames() ?? Array.Empty<string>(); } catch { resourceNames = Array.Empty<string>(); } list.Add(new PluginResources { OwnerName = pluginDisplayName, ResourceNames = resourceNames }); } return list; } private static void CollectJotunnManagerMappings(string managerTypeName, string collectionMemberName, int priority, HashSet<string> lookupCandidates, Dictionary<string, OwnerMapping> mappings) { Assembly[] loadedAssemblies = GetLoadedAssemblies(); foreach (Assembly assembly in loadedAssemblies) { Type type = SafeGetType(assembly, managerTypeName); if (type == null) { continue; } object obj = TryGetStaticMemberValue(type, "Instance"); if (obj == null || !TryGetRawMemberValue(obj, collectionMemberName, out object value)) { continue; } string fallbackOwnerName = ResolveAssemblyOwnerName(assembly); foreach (object item in EnumerateCollectionValues(value)) { string text = NormalizePrefabName(GetPrefabNameFromHolder(item)); if (lookupCandidates.Contains(text)) { string ownerName = ResolveOwnerNameFromSourceModHolder(item, fallbackOwnerName); TryAddOwnerMapping(mappings, text, ownerName, priority); } } } } private static void CollectLocationManagerTemplateMappings(HashSet<string> lookupCandidates, Dictionary<string, OwnerMapping> mappings) { Assembly[] loadedAssemblies = GetLoadedAssemblies(); foreach (Assembly assembly in loadedAssemblies) { FieldInfo fieldInfo = SafeGetType(assembly, "LocationManager.Location")?.GetField("registeredLocations", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (fieldInfo == null) { continue; } object value; try { value = fieldInfo.GetValue(null); } catch { continue; } string ownerName = ResolveAssemblyOwnerName(assembly); foreach (object item in EnumerateCollectionValues(value)) { string text = NormalizePrefabName(GetPrefabNameFromHolder(item)); if (lookupCandidates.Contains(text)) { TryAddOwnerMapping(mappings, text, ownerName, 1); } } } } private static IEnumerable<object> EnumerateCollectionValues(object? value) { object obj; if (!(value is IDictionary dictionary)) { obj = (value as IEnumerable) ?? Array.Empty<object>(); } else { IEnumerable values = dictionary.Values; obj = values; } IEnumerable enumerable = (IEnumerable)obj; foreach (object item in enumerable) { if (item != null) { yield return item; } } } private static string? GetPrefabNameFromHolder(object? holder) { if (holder == null) { return null; } GameObject val = (GameObject)((holder is GameObject) ? holder : null); if (val != null) { return ((Object)val).name; } Component val2 = (Component)((holder is Component) ? holder : null); if (val2 != null) { if (!((Object)(object)val2.gameObject != (Object)null)) { return ((Object)val2).name; } return ((Object)val2.gameObject).name; } if (TryGetRawMemberValue(holder, "Prefab", out object value)) { GameObject val3 = (GameObject)((value is GameObject) ? value : null); if (val3 != null) { return ((Object)val3).name; } Component val4 = (Component)((value is Component) ? value : null); if (val4 != null) { if (!((Object)(object)val4.gameObject != (Object)null)) { return ((Object)val4).name; } return ((Object)val4.gameObject).name; } } string[] array = new string[2] { "Location", "location" }; foreach (string memberName in array) { if (!TryGetRawMemberValue(holder, memberName, out object value2)) { continue; } GameObject val5 = (GameObject)((value2 is GameObject) ? value2 : null); if (val5 != null) { return ((Object)val5).name; } Component val6 = (Component)((value2 is Component) ? value2 : null); if (val6 != null) { if (!((Object)(object)val6.gameObject != (Object)null)) { return ((Object)val6).name; } return ((Object)val6.gameObject).name; } } if (TryGetRawMemberValue(holder, "ZoneLocation", out object value3) && value3 != null && TryGetRawMemberValue(value3, "m_prefabName", out object value4)) { return value4?.ToString(); } return null; } private static string ResolveOwnerNameFromSourceModHolder(object holder, string fallbackOwnerName) { if (TryGetRawMemberValue(holder, "SourceMod", out object value) && value != null && TryResolvePluginOwnerName(value, out string ownerName)) { return ownerName; } return fallbackOwnerName; } private static bool TryResolvePluginOwnerName(object sourceMod, out string ownerName) { ownerName = ""; if (TryGetRawMemberValue(sourceMod, "GUID", out object value)) { string text = (value?.ToString() ?? "").Trim(); if (text.Length > 0 && Chainloader.PluginInfos.TryGetValue(text, out var value2)) { ownerName = GetPluginDisplayName(value2.Metadata.Name, value2.Metadata.GUID); return true; } if (text.Length > 0) { ownerName = text; return true; } } if (TryGetRawMemberValue(sourceMod, "Name", out object value3)) { ownerName = (value3?.ToString() ?? "").Trim(); return ownerName.Length > 0; } return false; } private static string ResolveAssemblyOwnerName(Assembly assembly) { foreach (PluginInfo value in Chainloader.PluginInfos.Values) { Assembly assembly2 = ((object)value.Instance)?.GetType().Assembly; if (!(assembly2 == null) && (object)assembly2 == assembly) { return GetPluginDisplayName(value.Metadata.Name, value.Metadata.GUID); } } return NormalizeOwnerName(assembly.GetName().Name ?? assembly.FullName ?? ""); } private static string GetPluginDisplayName(string? pluginName, string? pluginGuid) { string text = NormalizeOwnerName(pluginName); if (!string.Equals(text, "Unknown / Untracked", StringComparison.OrdinalIgnoreCase)) { return text; } return NormalizeOwnerName(pluginGuid); } private static void TryAddOwnerMapping(Dictionary<string, OwnerMapping> mappings, string prefabName, string ownerName, int priority) { string text = NormalizePrefabName(prefabName); string text2 = NormalizeOwnerName(ownerName); if (text.Length != 0 && !string.Equals(text2, "Unknown / Untracked", StringComparison.OrdinalIgnoreCase) && (!mappings.TryGetValue(text, out var value) || value.Priority > priority)) { mappings[text] = new OwnerMapping(text2, priority); } } private static void EnsureVanillaCatalogLoaded() { if (_vanillaCatalogState != VanillaCatalogState.Uninitialized) { return; } string[] array = GetVanillaManifestPaths().Where(File.Exists).ToArray(); if (array.Length == 0) { _vanillaCatalogState = VanillaCatalogState.Unavailable; WarnVanillaCatalogFailure("Vanilla prefab manifests were not found under '" + GetVanillaManifestDirectoryPath() + "'."); return; } int num = 0; string text = ""; Exception ex = null; string[] array2 = array; foreach (string text2 in array2) { try { ReadVanillaManifest(text2); num++; } catch (Exception ex2) { if (ex == null) { ex = ex2; } if (text.Length == 0) { text = text2; } } } if (num > 0) { _vanillaCatalogState = VanillaCatalogState.Loaded; if (ex != null) { WarnVanillaCatalogFailure("Failed to read one vanilla prefab manifest at '" + text + "'. Other manifests remain available. " + ex.GetType().Name + ": " + ex.Message); } } else { VanillaPrefabNames.Clear(); VanillaAssetIds.Clear(); _vanillaCatalogState = VanillaCatalogState.Unavailable; WarnVanillaCatalogFailure("Failed to read vanilla prefab manifests under '" + GetVanillaManifestDirectoryPath() + "'. " + (ex?.GetType().Name ?? "Unknown error") + ": " + (ex?.Message ?? "No manifest could be read.")); } } private static void ReadVanillaManifest(string manifestPath) { HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (string item in File.ReadLines(manifestPath)) { int num = item.IndexOf("asset ID:", StringComparison.OrdinalIgnoreCase); if (num >= 0) { string text = item.Substring(num + "asset ID:".Length).Trim().ToLowerInvariant(); if (text.Length > 0) { hashSet2.Add(text); } } int num2 = item.IndexOf("path in bundle:", StringComparison.OrdinalIgnoreCase); if (num2 < 0) { continue; } string text2 = item.Substring(num2 + "path in bundle:".Length).Trim(); if (text2.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text2); if (!string.IsNullOrWhiteSpace(fileNameWithoutExtension)) { hashSet.Add(fileNameWithoutExtension.Trim()); } } } VanillaPrefabNames.UnionWith(hashSet); VanillaAssetIds.UnionWith(hashSet2); } private static string BuildSnapshotSignature(IReadOnlyCollection<AltarPrefabOwnerSource> prefabSources) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("location-owner-v4"); foreach (AltarPrefabOwnerSource prefabSource in prefabSources) { stringBuilder.Append("prefab:").Append(prefabSource.PrefabName).Append(':') .Append(prefabSource.SourcePrefabName) .Append(':') .Append(GetStableAssetIdSignature(prefabSource.PrefabAssetId)) .AppendLine(); } foreach (PluginInfo item in Chainloader.PluginInfos.Values.OrderBy<PluginInfo, string>((PluginInfo pluginInfo) => pluginInfo.Metadata.GUID ?? "", StringComparer.OrdinalIgnoreCase)) { Assembly assembly = ((object)item.Instance)?.GetType().Assembly; stringBuilder.Append("plugin:").Append(item.Metadata.GUID ?? "").Append(':') .Append(item.Metadata.Name ?? "") .Append(':') .Append(GetAssemblyModuleVersionId(assembly)) .AppendLine(); } Assembly[] loadedAssemblies = GetLoadedAssemblies(); foreach (Assembly assembly2 in loadedAssemblies) { stringBuilder.Append("assembly:").Append(assembly2.FullName ?? assembly2.GetName().Name ?? "").Append(':') .Append(GetAssemblyModuleVersionId(assembly2)) .AppendLine(); } try { foreach (string item2 in (from bundle in AssetBundle.GetAllLoadedAssetBundles() select ((bundle != null) ? ((Object)bundle).name : null) ?? "" into bundleName where bundleName.Length > 0 select bundleName).OrderBy<string, string>((string bundleName) => bundleName, StringComparer.OrdinalIgnoreCase)) { stringBuilder.Append("bundle:").AppendLine(item2); } } catch { stringBuilder.AppendLine("bundle:<unavailable>"); } foreach (string vanillaManifestPath in GetVanillaManifestPaths()) { stringBuilder.Append("vanilla:").Append(Path.GetFileName(vanillaManifestPath)).Append(':'); if (File.Exists(vanillaManifestPath)) { FileInfo fileInfo = new FileInfo(vanillaManifestPath); stringBuilder.Append(fileInfo.Length).Append(':').Append(fileInfo.LastWriteTimeUtc.Ticks) .AppendLine(); } else { stringBuilder.AppendLine("<missing>"); } } using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(stringBuilder.ToString())); StringBuilder stringBuilder2 = new StringBuilder(array.Length * 2); byte[] array2 = array; foreach (byte b in array2) { stringBuilder2.Append(b.ToString("x2")); } return stringBuilder2.ToString(); } private static string GetStableAssetIdSignature(string assetId) { string text = (assetId ?? "").Trim().ToLowerInvariant(); if (!text.StartsWith("000000010000000100000001", StringComparison.Ordinal)) { return text; } return "<runtime>"; } private static Assembly[] GetLoadedAssemblies() { return (from assembly in AppDomain.CurrentDomain.GetAssemblies() where !assembly.IsDynamic select assembly).OrderBy<Assembly, string>((Assembly assembly) => assembly.FullName ?? assembly.GetName().Name ?? "", StringComparer.OrdinalIgnoreCase).ToArray(); } private static string GetAssemblyModuleVersionId(Assembly? assembly) { if (assembly == null) { return ""; } try { return assembly.ManifestModule.ModuleVersionId.ToString("N"); } catch { return ""; } } private static bool TryLoadSnapshotFromCache(string signature, IReadOnlyCollection<string> prefabNames, out Dictionary<string, string> owners) { owners = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); string cachePath = GetCachePath(); if (!File.Exists(cachePath)) { return false; } try { string[] array = File.ReadAllLines(cachePath); if (array.Length < 2 || !string.Equals(array[0], "v1", StringComparison.Ordinal) || !string.Equals(array[1], signature, StringComparison.Ordinal)) { return false; } for (int i = 2; i < array.Length; i++) { string[] array2 = array[i].Split(new char[1] { '\t' }); if (array2.Length == 2) { string text = DecodeCacheField(array2[0]); string value = NormalizeOwnerName(DecodeCacheField(array2[1])); if (text.Length > 0) { owners[text] = value; } } } return prefabNames.All(owners.ContainsKey) && owners.Values.All((string ownerName) => !string.Equals(ownerName, "Unknown / Untracked", StringComparison.OrdinalIgnoreCase)); } catch { owners.Clear(); return false; } } private static void SaveSnapshotToCache(string signature, IReadOnlyDictionary<string, string> owners) { if (owners.Values.Any((string ownerName) => string.Equals(ownerName, "Unknown / Untracked", StringComparison.OrdinalIgnoreCase))) { DeleteSnapshotCache(); return; } try { string cachePath = GetCachePath(); Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("v1"); stringBuilder.AppendLine(signature); foreach (KeyValuePair<string, string> item in owners.OrderBy<KeyValuePair<string, string>, string>((KeyValuePair<string, string> pair) => pair.Key, StringComparer.OrdinalIgnoreCase)) { stringBuilder.Append(EncodeCacheField(item.Key)).Append('\t').Append(EncodeCacheField(item.Value)) .AppendLine(); } string text = stringBuilder.ToString(); if (!string.Equals(File.Exists(cachePath) ? File.ReadAllText(cachePath) : "", text, StringComparison.Ordinal)) { File.WriteAllText(cachePath, text, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } } catch (Exception ex) { if (!_cacheWarningLogged) { _cacheWarningLogged = true; BossRulesPlugin.BossRulesLogger.LogWarning((object)("Failed to cache altar prefab owner mappings. " + ex.GetType().Name + ": " + ex.Message)); } } } private static void DeleteSnapshotCache() { try { string cachePath = GetCachePath(); if (File.Exists(cachePath)) { File.Delete(cachePath); } } catch (Exception ex) { if (!_cacheWarningLogged) { _cacheWarningLogged = true; BossRulesPlugin.BossRulesLogger.LogWarning((object)("Failed to invalidate altar prefab owner cache. " + ex.GetType().Name + ": " + ex.Message)); } } } private static string GetCachePath() { return Path.Combine(BossRulesPlugin.ConfigDirectoryPath, "cache", ".altar-prefab-owner-cache.txt"); } private static IEnumerable<string> GetVanillaManifestPaths() { string manifestDirectoryPath = GetVanillaManifestDirectoryPath(); yield return Path.Combine(manifestDirectoryPath, "manifest"); yield return Path.Combine(manifestDirectoryPath, "manifest_extended"); } private static string GetVanillaManifestDirectoryPath() { return Path.Combine(Application.dataPath, "StreamingAssets", "SoftRef"); } private static string EncodeCacheField(string value) { return Convert.ToBase64String(Encoding.UTF8.GetBytes(value ?? "")); } private static string DecodeCacheField(string value) { try { return Encoding.UTF8.GetString(Convert.FromBase64String(value ?? "")); } catch { return ""; } } private static AltarPrefabOwnerSnapshot BuildUnknownSnapshot(IEnumerable<string> prefabNames) { return new AltarPrefabOwnerSnapshot(prefabNames.ToDictionary<string, string, string>((string prefabName) => prefabName, (string _) => "Unknown / Untracked", StringComparer.OrdinalIgnoreCase)); } private static void WarnResolverFailure(Exception exception) { if (!_resolverWarningLogged) { _resolverWarningLogged = true; BossRulesPlugin.BossRulesLogger.LogWarning((object)("Failed to resolve altar prefab owners; reference entries will be grouped under 'Unknown / Untracked'. " + exception.GetType().Name + ": " + exception.Message)); } } private static void WarnVanillaCatalogFailure(string reason) { if (!_vanillaCatalogWarningLogged) { _vanillaCatalogWarningLogged = true; BossRulesPlugin.BossRulesLogger.LogWarning((object)(reason + " Unresolved altar location owners will be grouped under 'Unknown / Untracked'.")); } } private static Type? SafeGetType(Assembly assembly, string fullTypeName) { try { return assembly.GetType(fullTypeName, throwOnError: false, ignoreCase: false); } catch { return null; } } private static object? TryGetStaticMemberValue(Type type, string memberName) { try { PropertyInfo property = type.GetProperty(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(null, null); } return type.GetField(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(null); } catch { return null; } } private static bool TryGetRawMemberValue(object instance, string memberName, out object? value) { value = null; Type type = instance.GetType(); while (type != null) { try { PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { value = property.GetValue(instance, null); return true; } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { value = field.GetValue(instance); return true; } } catch { return false; } type = type.BaseType; } return false; } private static string NormalizePrefabName(string? prefabName) { return TrimCloneSuffix((prefabName ?? "").Trim()); } private static string TrimCloneSuffix(string prefabName) { if (!prefabName.EndsWith("(Clone)", StringComparison.Ordinal)) { return prefabName; } return prefabName.Substring(0, prefabName.Length - "(Clone)".Length).TrimEnd(Array.Empty<char>()); } } internal sealed class BossRuleConfigurationSection { [YamlMember(Order = 1)] public BossDespawnConfigurationDefinition? Despawn { get; set; } [YamlMember(Order = 2)] public BossTamedPressureDefinition? BossTamedPressure { get; set; } [YamlMember(Order = 3)] public BossRuleLocalizationDefinition? Localization { get; set; } } internal sealed class BossDespawnConfigurationDefinition { [YamlMember(Order = 1)] public string? Defaults { get; set; } [YamlMember(Order = 2)] public List<string>? Rules { get; set; } } internal sealed class BossRuleLocalizationDefinition { [YamlMember(Order = 1)] public string? MessageDespawnStart { get; set; } [YamlMember(Order = 2)] public string? MessageDespawnReminder { get; set; } [YamlMember(Order = 3)] public string? MessageDespawnCanceled { get; set; } [YamlMember(Order = 4)] public string? MessageBossTamedPressure { get; set; } [YamlMember(Order = 5)] public string? MessageForsakenPowerRotate { get; set; } } internal sealed class BossDespawnDefinition { internal string Prefab { get; } public float? DespawnRange { get; set; } public float? DespawnDelay { get; set; } public bool? Refunds { get; set; } internal BossDespawnDefinition(string prefab, float? despawnRange, float? despawnDelay, bool? refunds) { Prefab = prefab; DespawnRange = despawnRange; DespawnDelay = despawnDelay; Refunds = refunds; } } internal sealed class BossRuleConfigurationState { internal const float FallbackDespawnRange = 64f; internal const float FallbackDespawnDelaySeconds = 90f; internal static BossRuleConfigurationState Empty => new BossRuleConfigurationState(); internal float DefaultDespawnRange { get; set; } = 64f; internal float DefaultDespawnDelaySeconds { get; set; } = 90f; internal List<BossDespawnDefinition> DespawnRules { get; } = new List<BossDespawnDefinition>(); internal BossTamedPressureDefinition? BossTamedPressureRule { get; set; } } internal sealed class BossTamedPressureDefinition { [YamlMember(Order = 1)] public List<string>? BossPrefabs { get; set; } [YamlMember(Order = 2)] public List<string>? ExcludedBossPrefabs { get; set; } [YamlMember(Order = 3)] public BossTamedPressureTargetsDefinition? Targets { get; set; } [YamlMember(Order = 4)] public BossTamedPressurePressureDefinition? Pressure { get; set; } } internal sealed class BossTamedPressureTargetsDefinition { [YamlMember(Order = 1)] public float? Range { get; set; } [YamlMember(Order = 2)] public int? MaxPerBoss { get; set; } [YamlMember(Order = 3)] public List<string>? ExcludedTamedPrefabs { get; set; } [YamlMember(Order = 4)] public List<string>? ExtraPressuredPrefabs { get; set; } } internal sealed class BossTamedPressurePressureDefinition { [YamlMember(Order = 1)] public float? DamagePercentPerSecond { get; set; } [YamlMember(Order = 2)] public float? IncomingDamageMultiplier { get; set; } [YamlMember(Order = 3)] public float? OutgoingDamageMultiplier { get; set; } } internal static class BossRuleConfiguration { private static readonly IDeserializer Deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build(); private static bool _reportedLegacyLocalization; internal static bool TryParse(string yaml, string source, out BossRuleConfigurationState state) { state = BossRuleConfigurationState.Empty; try { BossRuleConfigurationSection bossRuleConfigurationSection = (string.IsNullOrWhiteSpace(yaml) ? new BossRuleConfigurationSection() : Deserializer.Deserialize<BossRuleConfigurationSection>(yaml)); state = Normalize(bossRuleConfigurationSection ?? new BossRuleConfigurationSection()); BossRulesPlugin.BossRulesLogger.LogInfo((object)$"Loaded boss rules YAML from {source}: {state.DespawnRules.Count} despawn entries, {((state.BossTamedPressureRule != null) ? 1 : 0)} boss tamed pressure entries."); return true; } catch (Exception ex) { BossRulesPlugin.BossRulesLogger.LogError((object)("Rejected boss rules YAML from " + source + ". Keeping the previous configuration. " + ex.GetType().Name + ": " + ex.Message)); return false; } } private static BossRuleConfigurationState Normalize(BossRuleConfigurationSection section) { BossRuleConfigurationState bossRuleConfigurationState = new BossRuleConfigurationState(); (bossRuleConfigurationState.DefaultDespawnRange, bossRuleConfigurationState.DefaultDespawnDelaySeconds) = ParseDespawnDefaults(section.Despawn?.Defaults); foreach (string item in section.Despawn?.Rules ?? new List<string>()) { bossRuleConfigurationState.DespawnRules.Add(ParseDespawnRule(item)); } if (section.BossTamedPressure != null) { NormalizeBossTamedPressure(section.BossTamedPressure); bossRuleConfigurationState.BossTamedPressureRule = section.BossTamedPressure; } if (section.Localization != null && !_reportedLegacyLocalization) { _reportedLegacyLocalization = true; BossRulesPlugin.BossRulesLogger.LogWarning((object)"BossRules.yml 'localization' is deprecated and ignored. Use BossRules.<Language>.yml or .json localization files instead."); } return bossRuleConfigurationState; } private static (float Range, float DelaySeconds) ParseDespawnDefaults(string? rawDefaults) { if (string.IsNullOrWhiteSpace(rawDefaults)) { return (Range: 64f, DelaySeconds: 90f); } string text = rawDefaults.Trim(); string[] array = text.Split(new char[1] { ',' }); if (array.Length > 2) { throw new FormatException("despawn.defaults '" + text + "' has too many values. Expected 'range, delaySeconds'."); } float item = ParseDefaultFloat(array, 0, "range", text, 64f); float item2 = ParseDefaultFloat(array, 1, "delaySeconds", text, 90f); return (Range: item, DelaySeconds: item2); } private static float ParseDefaultFloat(string[] parts, int index, string fieldName, string rawDefaults, float fallback) { if (parts.Length <= index) { return fallback; } string text = parts[index].Trim(); if (text.Length == 0) { return fallback; } if (float.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var result)) { return result; } throw new FormatException("despawn.defaults '" + rawDefaults + "' has invalid " + fieldName + " value '" + text + "'."); } private static BossDespawnDefinition ParseDespawnRule(string rawRule) { string text = (rawRule ?? "").Trim(); if (text.Length == 0) { throw new FormatException("Empty despawn row. Expected '- prefab, despawnRange, despawnDelay, refunds'."); } string[] array = text.Split(new char[1] { ',' }); if (array.Length > 4) { throw new FormatException("Despawn row '" + text + "' has too many values. Expected '- prefab, despawnRange, despawnDelay, refunds'."); } string text2 = array[0].Trim(); if (text2.Length == 0) { throw new FormatException("Despawn row '" + text + "' has an empty prefab name."); } return new BossDespawnDefinition(text2, ParseOptionalFloat(array, 1, "despawnRange", text), ParseOptionalFloat(array, 2, "despawnDelay", text), ParseOptionalBool(array, 3, "refunds", text)); } private static float? ParseOptionalFloat(string[] parts, int index, string fieldName, string rawRule) { if (parts.Length <= index) { return null; } string text = parts[index].Trim(); if (text.Length == 0) { return null; } if (float.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var result)) { return result; } throw new FormatException("Despawn row '" + rawRule + "' has invalid " + fieldName + " value '" + text + "'."); } private static bool? ParseOptionalBool(string[] parts, int index, string fieldName, string rawRule) { if (parts.Length <= index) { return null; } string text = parts[index].Trim(); if (text.Length == 0) { return null; } if (bool.TryParse(text, out var result)) { return result; } throw new FormatException("Despawn row '" + rawRule + "' has invalid " + fieldName + " value '" + text + "'. Use true or false."); } private static void NormalizeBossTamedPressure(BossTamedPressureDefinition? definition) { if (definition != null) { definition.BossPrefabs = NormalizeStringList(definition.BossPrefabs); definition.ExcludedBossPrefabs = NormalizeStringList(definition.ExcludedBossPrefabs); if (definition.Targets != null) { definition.Targets.ExcludedTamedPrefabs = NormalizeStringList(definition.Targets.ExcludedTamedPrefabs); definition.Targets.ExtraPressuredPrefabs = NormalizeStringList(definition.Targets.ExtraPressuredPrefabs); } } } private static List<string>? NormalizeStringList(List<string>? values) { List<string> list = (from value in values?.Select((string value) => (value ?? "").Trim()) where value.Length > 0 select value).Distinct<string>(StringComparer.OrdinalIgnoreCase).ToList(); if (list == null || list.Count <= 0) { return null; } return list; } } internal static class BossRuleConfigurationFiles { internal static void EnsureDefaultFile() { EnsureTextFile(BossRulesPlugin.RulesYamlFilePath, BuildDefaultRulesYaml()); } private static void EnsureTextFile(string path, string content) { if (!File.Exists(path)) { File.WriteAllText(path, content); BossRulesPlugin.BossRulesLogger.LogInfo((object)("Created " + path + ".")); } } private static string BuildDefaultRulesYaml() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# BossRules runtime rules"); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("despawn:"); stringBuilder.AppendLine(" defaults: 64, 90 # default despawnRange, despawnDelaySeconds."); stringBuilder.AppendLine(" rules:"); stringBuilder.AppendLine(" # - prefab, despawnRange, despawnDelay, refunds"); stringBuilder.AppendLine(" # Empty or omitted despawnRange/despawnDelay uses despawn.defaults."); stringBuilder.AppendLine(" # despawnRange: 0 disables despawn for that prefab."); stringBuilder.AppendLine(" # refunds omitted or empty: true. Use false to disable altar offering refunds."); stringBuilder.AppendLine(" - Fader, 64, 90, true # Boss prefabs are auto-detected, but non-boss Character prefabs can also be listed here for despawn rules."); stringBuilder.AppendLine(); stringBuilder.AppendLine("bossTamedPressure:"); stringBuilder.AppendLine(" bossPrefabs: [Eikthyr] # Extra source boss prefabs added to the auto-detected boss set"); stringBuilder.AppendLine(" excludedBossPrefabs: [] # Boss prefabs to ignore from auto-detected and bossPrefabs sources"); stringBuilder.AppendLine(" targets:"); stringBuilder.AppendLine(" range: 32 # Clamp: 0~128. Horizontal XZ range around each boss"); stringBuilder.AppendLine(" maxPerBoss: 4 # Clamp: 1~128. Maximum pressured targets per boss per scan"); stringBuilder.AppendLine(" excludedTamedPrefabs: [] # Tamed MonsterAI prefabs excluded from the default pressured target set"); stringBuilder.AppendLine(" extraPressuredPrefabs: [] # Character prefabs pressured even when not tamed"); stringBuilder.AppendLine(" pressure:"); stringBuilder.AppendLine(" damagePercentPerSecond: 0.01 # Clamp: 0~1. 0.01 = 1% of max health per second"); stringBuilder.AppendLine(" incomingDamageMultiplier: 1.25 # Clamp: 0~10. Multiplies damage received while affected"); stringBuilder.AppendLine(" outgoingDamageMultiplier: 0.75 # Clamp: 0~10. Multiplies damage dealt while affected"); return stringBuilder.ToString(); } } internal static class BossRulesConsoleCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__InspectRuntimeTarget; public static ConsoleOptionsFetcher <1>__GetInspectTabOptions; public static ConsoleEvent <2>__HandleBossStoneCommand; public static ConsoleOptionsFetcher <3>__GetBossStoneTabOptions; } private const string InspectCommandName = "bossrules:inspect"; private const string BossStoneCommandName = "bossrules:bossstone"; private static readonly List<string> InspectTabOptions = new List<string> { "bossstone" }; private static readonly List<string> BossStoneTabOptions = new List<string> { "reset" }; private static bool _registered; internal static void Register() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Expected O, but got Unknown //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Expected O, but got Unknown if (!_registered) { _registered = true; object obj = <>O.<0>__InspectRuntimeTarget; if (obj == null) { ConsoleEvent val = InspectRuntimeTarget; <>O.<0>__InspectRuntimeTarget = val; obj = (object)val; } object obj2 = <>O.<1>__GetInspectTabOptions; if (obj2 == null) { ConsoleOptionsFetcher val2 = GetInspectTabOptions; <>O.<1>__GetInspectTabOptions = val2; obj2 = (object)val2; } new ConsoleCommand("bossrules:inspect", "Inspect the current hovered/aimed BossRules runtime target. Currently supports: bossstone.", (ConsoleEvent)obj, false, false, false, false, false, (ConsoleOptionsFetcher)obj2, false, false, false); object obj3 = <>O.<2>__HandleBossStoneCommand; if (obj3 == null) { ConsoleEvent val3 = HandleBossStoneCommand; <>O.<2>__HandleBossStoneCommand = val3; obj3 = (object)val3; } object obj4 = <>O.<3>__GetBossStoneTabOptions; if (obj4 == null) { ConsoleOptionsFetcher val4 = GetBossStoneTabOptions; <>O.<3>__GetBossStoneTabOptions = val4; obj4 = (object)val4; } new ConsoleCommand("bossrules:bossstone", "Reset per-player boss stone state. Syntax: bossrules:bossstone reset <exactPlayerName>", (ConsoleEvent)obj3, true, false, false, false, false, (ConsoleOptionsFetcher)obj4, false, false, true); } } private static List<string> GetInspectTabOptions() { return InspectTabOptions; } private static List<string> GetBossStoneTabOptions() { return BossStoneTabOptions; } private static void InspectRuntimeTarget(ConsoleEventArgs args) { string[] lines; string error; if (((args.Length >= 2) ? (args[1] ?? "").Trim().ToLowerInvariant() : "") != "bossstone") { Terminal context = args.Context; if (context != null) { context.AddString("Syntax: bossrules:inspect bossstone"); } } else if (BossStonePerPlayerRuntime.TryInspectCurrentTarget(out lines, out error)) { string[] array = lines; foreach (string text in array) { Terminal context2 = args.Context; if (context2 != null) { context2.AddString(text); } } } else { Terminal context3 = args.Context; if (context3 != null) { context3.AddString(error); } } } private static void HandleBossStoneCommand(ConsoleEventArgs args) { if (((args.Length >= 2) ? (args[1] ?? "").Trim().ToLowerInvariant() : "") != "reset") { Terminal context = args.Context; if (context != null) { context.AddString("Syntax: bossrules:bossstone reset <exactPlayerName>"); } return; } BossStonePerPlayerRuntime.TryRequestReset((args.FullLine.Length > "bossrules:bossstone reset".Length) ? args.FullLine.Substring("bossrules:bossstone reset".Length).Trim() : "", out string message); Terminal context2 = args.Context; if (context2 != null) { context2.AddString(message); } } } internal static class BossRulesLocalization { internal const string MessageDespawnStartKey = "bossrules_message_despawn_start"; internal const string MessageDespawnReminderKey = "bossrules_message_despawn_reminder"; internal const string MessageDespawnCanceledKey = "bossrules_message_despawn_canceled"; internal const string MessageBossTamedPressureKey = "bossrules_message_boss_tamed_pressure"; internal const string MessageForsakenPowerRotateKey = "bossrules_message_forsaken_power_rotate"; private static readonly Dictionary<string, string> EnglishDefaults = new Dictionary<string, string>(StringComparer.Ordinal) { ["bossrules_message_despawn_start"] = "{name} will despawn in {seconds}s unless someone returns.", ["bossrules_message_despawn_reminder"] = "{name} will despawn in {seconds}s.", ["bossrules_message_despawn_canceled"] = "{name} despawn canceled.", ["bossrules_message_boss_tamed_pressure"] = "Tamed creatures near a boss are weakened.", ["bossrules_message_forsaken_power_rotate"] = "Rotate" }; private static readonly Dictionary<string, string> EnglishNameCache = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly HashSet<string> MissingEnglishNameCache = new HashSet<string>(StringComparer.Ordinal); internal static string Text(string key) { string text = NormalizeKey(key); Localization instance = Localization.instance; if (instance != null && instance.m_translations.TryGetValue(text, out var value) && !string.IsNullOrWhiteSpace(value)) { return value; } if (Localizer.TryGetEnglishText(text, out string text2)) { return text2; } if (!EnglishDefaults.TryGetValue(text, out string value2)) { return text; } return value2; } internal static string FormatDespawnMessage(string messageKey, string? nameLocalizationKey, string? prefabName, int remainingSeconds) { string newValue = ResolveCharacterName(nameLocalizationKey, prefabName); return Text(messageKey).Replace("{name}", newValue).Replace("{seconds}", Math.Max(0, remainingSeconds).ToString(CultureInfo.InvariantCulture)); } internal static string ResolveCharacterName(string? nameLocalizationKey, string? prefabName) { string text = (nameLocalizationKey ?? "").Trim(); if (text.Length == 0) { text = ResolveNameTokenFromPrefab(prefabName); } if (text.Length > 0 && !text.StartsWith("$", StringComparison.Ordinal)) { Localization instance = Localization.instance; string text2 = ((instance != null) ? instance.Localize(text) : null) ?? text; if (IsUsableTranslation(text2, text)) { return text2; } } string text3 = NormalizeKey(text); if (text3.Length > 0) { Localization instance2 = Localization.instance; if (instance2 != null && instance2.m_translations.TryGetValue(text3, out var value) && IsUsableTranslation(value, text3)) { return value; } if (TryGetEnglishCharacterName(instance2, text3, out string englishText)) { return englishText; } } string text4 = Utils.GetPrefabName(prefabName ?? "").Trim(); if (text4.Length <= 0) { return "Target"; } return text4; } private static string ResolveNameTokenFromPrefab(string? prefabName) { string text = Utils.GetPrefabName(prefabName ?? "").Trim(); if (text.Length == 0) { return ""; } ZNetScene instance = ZNetScene.instance; GameObject obj = ((instance != null) ? instance.GetPrefab(text) : null); return ((obj != null) ? obj.GetComponent<Character>() : null)?.m_name?.Trim() ?? ""; } private static bool TryGetEnglishCharacterName(Localization? localization, string key, out string englishText) { if (EnglishNameCache.TryGetValue(key, out englishText)) { return true; } englishText = ""; if (localization == null || MissingEnglishNameCache.Contains(key)) { return false; } if (Localizer.TryGetLoadedAssemblyEnglishText(key, out string text)) { EnglishNameCache[key] = text; englishText = text; return true; } try { string text2 = localization.TranslateSingleId(key, "English") ?? ""; if (!IsUsableTranslation(text2, key)) { MissingEnglishNameCache.Add(key); return false; } EnglishNameCache[key] = text2; englishText = text2; return true; } catch (Exception ex) { MissingEnglishNameCache.Add(key); BossRulesPlugin.BossRulesLogger.LogDebug((object)("English localization lookup failed for '$" + key + "'. " + ex.GetType().Name + ": " + ex.Message)); ret