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.0
BossRules.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.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.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; 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.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BossRules { [BepInPlugin("sighsorry.BossRules", "BossRules", "1.0.0")] 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.0"; 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 ConfigEntry<Toggle> _lockConfiguration; private IReadOnlyList<AltarConfigurationEntry> _altarEntries = Array.Empty<AltarConfigurationEntry>(); private BossRuleConfigurationState _rulesConfiguration = BossRuleConfigurationState.Empty; private IReadOnlyList<ForsakenPowerDefinition> _forsakenPowers = Array.Empty<ForsakenPowerDefinition>(); internal static BossRulesPlugin? Instance { get; private set; } 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 IReadOnlyList<AltarConfigurationEntry> AltarEntries => Instance?._altarEntries ?? Array.Empty<AltarConfigurationEntry>(); internal static BossRuleConfigurationState RulesConfiguration => Instance?._rulesConfiguration ?? BossRuleConfigurationState.Empty; internal static bool IsRuntimeServer() { if ((Object)(object)ZNet.instance != (Object)null) { return ZNet.instance.IsServer(); } return false; } private void Awake() { EnsureServerSyncInitialized(); Instance = this; 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); BossStonePerPlayerRuntime.Initialize(); BossRulesConsoleCommands.Register(); InitializeWatcher(); ((BaseUnityPlugin)this).Config.Save(); } private void Update() { ProcessQueuedYamlReload(); DataForgeStatusEffectBridge.ProcessDeferredSubscription(); AltarRuntime.ProcessPendingAltarSummonMarkers(); AltarRuntime.ProcessDeferredReapply(); AltarReferenceGenerator.TryAutoRefreshReferenceConfigurationFile(); ForsakenPowerRuntime.ProcessDeferredApply(); BossStonePerPlayerRuntime.EnsureRpcRegistered(); BossStonePerPlayerRuntime.ProcessPendingResetRequests(); DespawnRulesManager.ExecuteServerTick(); BossTamedPressureRuntime.ExecuteServerTick(); } private void OnDestroy() { if ((Object)(object)Instance == (Object)(object)this) { Instance = null; } if (_configSync != null) { ConfigSync.SourceOfTruthChanged -= HandleSourceOfTruthChanged; } _syncedAltarYaml.ValueChanged -= HandleSyncedAltarYamlChanged; _syncedRulesYaml.ValueChanged -= HandleSyncedRulesYamlChanged; _syncedForsakenPowersYaml.ValueChanged -= HandleSyncedForsakenPowersYamlChanged; _watcher?.Dispose(); _watcher = null; AltarRuntime.Shutdown(); BossStonePerPlayerRuntime.Shutdown(); AltarReferenceGenerator.ResetAutoRefresh(); BossRulesManager.ClearRuntimeState(); BossRulesRuntime.Reset(); DataForgeStatusEffectBridge.Shutdown(); _harmony.UnpatchSelf(); ((BaseUnityPlugin)this).Config.Save(); } private static void EnsureServerSyncInitialized() { if (_configSync == null) { _configSync = new ConfigSync("sighsorry.BossRules") { DisplayName = "BossRules", CurrentVersion = "1.0.0", MinimumRequiredVersion = "1.0.0" }; } } 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.Renamed += QueueYamlReload; _watcher.EnableRaisingEvents = true; } private void QueueYamlReload(object sender, FileSystemEventArgs args) { if (IsSourceOfTruth) { string fileName = Path.GetFileName(args.FullPath); if (string.Equals(fileName, "BossRules.altar.yml", StringComparison.OrdinalIgnoreCase) || string.Equals(fileName, "BossRules.yml", StringComparison.OrdinalIgnoreCase) || string.Equals(fileName, "BossRules.forsakenPowers.yml", StringComparison.OrdinalIgnoreCase)) { _reloadDueAt = Time.realtimeSinceStartup + 0.25f; } } } private void ProcessQueuedYamlReload() { if (!(_reloadDueAt < 0f) && !(Time.realtimeSinceStartup < _reloadDueAt)) { _reloadDueAt = -1f; LoadLocalAltarYamlAndPublish("file change"); LoadLocalRulesYamlAndPublish("file change"); LoadLocalForsakenPowersYamlAndPublish("file change"); } } 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; } _altarEntries = entries; 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; } _rulesConfiguration = state; BossRulesRuntime.Reload(BuildMergedRulesConfiguration()); return true; } private bool ApplyForsakenPowersYaml(string yaml, string source) { if (!ForsakenPowerConfiguration.TryParse(yaml, source, out IReadOnlyList<ForsakenPowerDefinition> entries)) { return false; } _forsakenPowers = entries; BossRulesRuntime.Reload(BuildMergedRulesConfiguration()); return true; } private BossRuleConfigurationState BuildMergedRulesConfiguration() { BossRuleConfigurationState bossRuleConfigurationState = new BossRuleConfigurationState(); bossRuleConfigurationState.DefaultDespawnRange = _rulesConfiguration.DefaultDespawnRange; bossRuleConfigurationState.DefaultDespawnDelaySeconds = _rulesConfiguration.DefaultDespawnDelaySeconds; bossRuleConfigurationState.DespawnRules.AddRange(_rulesConfiguration.DespawnRules); bossRuleConfigurationState.BossTamedPressureRules.AddRange(_rulesConfiguration.BossTamedPressureRules); bossRuleConfigurationState.ForsakenPowers.AddRange(_forsakenPowers); bossRuleConfigurationState.MessageDespawnStart = _rulesConfiguration.MessageDespawnStart; bossRuleConfigurationState.MessageDespawnReminder = _rulesConfiguration.MessageDespawnReminder; bossRuleConfigurationState.MessageDespawnCanceled = _rulesConfiguration.MessageDespawnCanceled; bossRuleConfigurationState.MessageBossTamedPressure = _rulesConfiguration.MessageBossTamedPressure; bossRuleConfigurationState.MessageForsakenPowerRotate = _rulesConfiguration.MessageForsakenPowerRotate; return bossRuleConfigurationState; } } 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; } = ""; [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 float _nextAutoRefreshAttemptAt; internal static void ResetAutoRefresh() { _autoRefreshDone = 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; } try { int entryCount; string content = BuildReferenceConfigurationContent(out entryCount); 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) { 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."); 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(); foreach (AltarReferenceEntry item in entries.OrderBy<AltarReferenceEntry, string>((AltarReferenceEntry entry) => entry.Prefab, StringComparer.OrdinalIgnoreCase)) { stringBuilder.AppendLine(SerializeReferenceEntry(item)); } 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>(); HashSet<string> capturedPrefabs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); DuplicateComponentWarnings.Clear(); foreach (ZoneLocation location in ZoneSystem.instance.m_locations) { if (TryCaptureReferenceEntry(location, capturedPrefabs, out AltarReferenceEntry entry) && entry != null) { list.Add(entry); } } return list; } private static bool TryCaptureReferenceEntry(ZoneLocation location, HashSet<string> capturedPrefabs, out AltarReferenceEntry? entry) { //IL_003b: 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(); 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); if (componentsInChildren.Length == 0 && componentsInChildren2.Length == 0) { return false; } WarnDuplicateComponent(zoneLocationPrefabName, "OfferingBowl", componentsInChildren.Length); entry = new AltarReferenceEntry { Prefab = zoneLocationPrefabName, OfferingBowl = ((componentsInChildren.Length != 0) ? ConvertReferenceOfferingBowl(AltarRuntime.CaptureOfferingBowlSnapshot(componentsInChildren[0])) : null), 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 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 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 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 List<BossTamedPressureDefinition> BossTamedPressureRules { get; } = new List<BossTamedPressureDefinition>(); internal List<ForsakenPowerDefinition> ForsakenPowers { get; } = new List<ForsakenPowerDefinition>(); internal string? MessageDespawnStart { get; set; } internal string? MessageDespawnReminder { get; set; } internal string? MessageDespawnCanceled { get; set; } internal string? MessageBossTamedPressure { get; set; } internal string? MessageForsakenPowerRotate { 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(); 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.BossTamedPressureRules.Count} 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.BossTamedPressureRules.Add(section.BossTamedPressure); } BossRuleLocalizationDefinition localization = section.Localization; if (localization != null && localization.MessageDespawnStart != null) { bossRuleConfigurationState.MessageDespawnStart = localization.MessageDespawnStart.Trim(); } if (localization != null && localization.MessageDespawnReminder != null) { bossRuleConfigurationState.MessageDespawnReminder = localization.MessageDespawnReminder.Trim(); } if (localization != null && localization.MessageDespawnCanceled != null) { bossRuleConfigurationState.MessageDespawnCanceled = localization.MessageDespawnCanceled.Trim(); } if (localization != null && localization.MessageBossTamedPressure != null) { bossRuleConfigurationState.MessageBossTamedPressure = localization.MessageBossTamedPressure.Trim(); } if (localization != null && localization.MessageForsakenPowerRotate != null) { bossRuleConfigurationState.MessageForsakenPowerRotate = localization.MessageForsakenPowerRotate.Trim(); } 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; } private static string? NormalizeOptionalString(string? value) { string text = (value ?? "").Trim(); if (text.Length <= 0) { return null; } return text; } } 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"); stringBuilder.AppendLine(); stringBuilder.AppendLine("localization:"); stringBuilder.AppendLine(" messageDespawnStart: \"{name} will despawn in {seconds}s unless someone returns.\""); stringBuilder.AppendLine(" messageDespawnReminder: \"{name} will despawn in {seconds}s.\""); stringBuilder.AppendLine(" messageDespawnCanceled: \"{name} despawn canceled.\""); stringBuilder.AppendLine(" messageBossTamedPressure: \"Tamed creatures near a boss are weakened.\""); stringBuilder.AppendLine(" messageForsakenPowerRotate: \"Rotate\""); 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 AltarRuntime { private sealed class AuthoredItemStandSlotTemplate { public string Path { get; set; } = ""; public Vector3 OfferingBowlLocalOffset { get; set; } } internal enum OfferingBowlBlockReason { None, SameBossNearby, RespawnCooldownActive } internal readonly struct OfferingBowlBlockResult { internal static OfferingBowlBlockResult None => default(OfferingBowlBlockResult); public bool Blocked { get; } public OfferingBowlBlockReason Reason { get; } public OfferingBowlBlockResult(bool blocked, OfferingBowlBlockReason reason) { Blocked = blocked; Reason = reason; } } private sealed class PendingAltarBossSpawn { public string BossPrefabName { get; set; } = ""; public int BossPrefabHash { get; set; } public Vector3 SpawnPoint { get; set; } public Vector3 RefundPoint { get; set; } public string RefundPayload { get; set; } = ""; public float ExpiresAt { get; set; } } private static readonly object Sync = new object(); private static readonly int OfferingBowlLastUseTicksKey = StringExtensionMethods.GetStableHashCode("BossRules.offering_bowl_last_use_ticks"); private static readonly Dictionary<string, List<AltarConfigurationEntry>> ActiveEntriesByPrefab = new Dictionary<string, List<AltarConfigurationEntry>>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<Location, string> RegisteredLocationPrefabs = new Dictionary<Location, string>(); private static readonly Dictionary<string, List<AuthoredItemStandSlotTemplate>> AuthoredItemStandSlotsByPrefab = new Dictionary<string, List<AuthoredItemStandSlotTemplate>>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<ItemStand, string> LooseItemStandAuthoredPathsByInstance = new Dictionary<ItemStand, string>(); private static bool _pendingGameDataReapply; private static bool _loggedPendingGameDataWait; private static readonly int AltarSummonKey = StringExtensionMethods.GetStableHashCode("BossRules.altar_summon"); private static readonly int AltarRefundsKey = StringExtensionMethods.GetStableHashCode("BossRules.altar_refunds"); private static readonly int AltarRefundPointKey = StringExtensionMethods.GetStableHashCode("BossRules.altar_refund_point"); private const float AltarSpawnMarkerMaxDistance = 128f; private const float AltarSpawnMarkerMaxDistanceSquared = 16384f; private const float AltarSpawnMarkerRetrySeconds = 5f; private const float AltarSpawnMarkerRetryIntervalSeconds = 0.25f; private static readonly List<PendingAltarBossSpawn> PendingAltarBossSpawns = new List<PendingAltarBossSpawn>(); private static readonly List<PendingAltarBossSpawn> PendingAltarBossSpawnRemovals = new List<PendingAltarBossSpawn>(); private static float _nextAltarSpawnMarkerRetryAt; internal static void Reload(IReadOnlyList<AltarConfigurationEntry> entries) { lock (Sync) { ActiveEntriesByPrefab.Clear(); AuthoredItemStandSlotsByPrefab.Clear(); LooseItemStandAuthoredPathsByInstance.Clear(); AltarItemStandHoverInfoFormatter.ClearRuntimeCaches(); _pendingGameDataReapply = true; _loggedPendingGameDataWait = false; foreach (AltarConfigurationEntry entry in entries) { if (entry.Enabled && HasOverride(entry)) { if (!ActiveEntriesByPrefab.TryGetValue(entry.Prefab, out List<AltarConfigurationEntry> value)) { value = new List<AltarConfigurationEntry>(); ActiveEntriesByPrefab[entry.Prefab] = value; } value.Add(entry); } } BossRulesDebugLog.Client($"Altar reload entries={entries.Count} activePrefabs={ActiveEntriesByPrefab.Count} gameDataReady={IsGameDataReady()} registeredLocations={RegisteredLocationPrefabs.Count}."); ReapplyRegisteredLocationsLocked(); } } internal static void Shutdown() { lock (Sync) { foreach (KeyValuePair<Location, string> item in RegisteredLocationPrefabs.ToList()) { Location key = item.Key; if ((Object)(object)key != (Object)null) { RestoreRoot(((Component)key).transform); } } RegisteredLocationPrefabs.Clear(); ActiveEntriesByPrefab.Clear(); AuthoredItemStandSlotsByPrefab.Clear(); LooseItemStandAuthoredPathsByInstance.Clear(); AltarItemStandHoverInfoFormatter.ClearRuntimeCaches(); _pendingGameDataReapply = false; _loggedPendingGameDataWait = false; } } internal static void ProcessDeferredReapply() { lock (Sync) { if (!_pendingGameDataReapply) { return; } if (!IsGameDataReady()) { if (!_loggedPendingGameDataWait) { _loggedPendingGameDataWait = true; BossRulesDebugLog.Client("Altar deferred reapply waiting for game data. " + DescribeGameDataState()); } } else { _pendingGameDataReapply = false; _loggedPendingGameDataWait = false; BossRulesDebugLog.Client($"Altar deferred reapply running. {DescribeGameDataState()} registeredLocations={RegisteredLocationPrefabs.Count} activePrefabs={ActiveEntriesByPrefab.Count}."); ReapplyRegisteredLocationsLocked(); ReapplyLoadedLooseOfferingBowlsLocked(); ReapplyLoadedLooseItemStandsLocked(); } } } internal static bool HasConfiguredPrefab(string prefabName) { lock (Sync) { return ActiveEntriesByPrefab.ContainsKey((prefabName ?? "").Trim()); } } internal static void RegisterLocation(Location? location) { if ((Object)(object)location == (Object)null) { return; } lock (Sync) { if (!AltarLocationResolver.TryResolveLocationPrefabName(location, out string prefabName)) { BossRulesDebugLog.Client("Altar register location skipped: prefab unresolved location=" + ((Object)location).name + "."); return; } RegisteredLocationPrefabs[location] = prefabName; BossRulesDebugLog.Client("Altar registered location prefab=" + prefabName + " location=" + ((Object)location).name + "."); ReconcileRootLocked(((Component)location).transform, prefabName); } } internal static void UnregisterLocation(Location? location) { if ((Object)(object)location == (Object)null) { return; } lock (Sync) { RegisteredLocationPrefabs.Remove(location); } } internal static void ReconcileSpawnedLocationRoot(GameObject? rootObject, string prefabName) { if ((Object)(object)rootObject == (Object)null) { return; } string text = (prefabName ?? "").Trim(); if (text.Length == 0) { return; } lock (Sync) { RefreshRegisteredLocationPrefabsLocked(rootObject.transform, text); ReconcileRootLocked(rootObject.transform, text); } } private static void RefreshRegisteredLocationPrefabsLocked(Transform root, string prefabName) { Location[] componentsInChildren = ((Component)root).GetComponentsInChildren<Location>(true); Location[] array = componentsInChildren; foreach (Location val in array) { if ((Object)(object)val != (Object)null) { RegisteredLocationPrefabs[val] = prefabName; } } if (componentsInChildren.Length != 0) { BossRulesDebugLog.Client($"Altar refreshed spawned location prefab cache prefab={prefabName} root={((Object)root).name} locations={componentsInChildren.Length}."); } } internal static void ReconcileLooseOfferingBowl(OfferingBowl? offeringBowl) { if ((Object)(object)offeringBowl == (Object)null || (Object)(object)((Component)offeringBowl).GetComponentInParent<Location>(true) != (Object)null) { return; } lock (Sync) { if (!AltarItemStandHoverInfoFormatter.TryResolveOfferingBowlContext(offeringBowl, out string locationPrefab, out Transform root)) { BossRulesDebugLog.Client("Altar loose offering bowl skipped: context unresolved bowl=" + ((Object)offeringBowl).name + "."); return; } BossRulesDebugLog.Client("Altar loose offering bowl context prefab=" + locationPrefab + " root=" + ((Object)root).name + " bowl=" + ((Object)offeringBowl).name + "."); ReconcileRootLocked(root, locationPrefab); } } internal static void ReconcileLooseItemStand(ItemStand? itemStand) { lock (Sync) { ReconcileLooseItemStandLocked(itemStand); } } internal static OfferingBowlBlockResult EvaluateOfferingBowlBlock(OfferingBowl? offeringBowl) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) lock (Sync) { if ((Object)(object)offeringBowl == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return OfferingBowlBlockResult.None; } if (BossRulesManager.ShouldBlockConfiguredSameBossSpawn(offeringBowl.m_bossPrefab, ((Component)offeringBowl).transform.position)) { return new OfferingBowlBlockResult(blocked: true, OfferingBowlBlockReason.SameBossNearby); } OfferingBowlRuntimeState component = ((Component)offeringBowl).GetComponent<OfferingBowlRuntimeState>(); if ((Object)(object)component == (Object)null || component.RespawnMinutes <= 0f) { return OfferingBowlBlockResult.None; } long offeringBowlLastUseTicks = GetOfferingBowlLastUseTicks(offeringBowl, component); if (offeringBowlLastUseTicks <= 0) { return OfferingBowlBlockResult.None; } return ((ZNet.instance.GetTime() - new DateTime(offeringBowlLastUseTicks)).TotalMinutes >= (double)component.RespawnMinutes) ? OfferingBowlBlockResult.None : new OfferingBowlBlockResult(blocked: true, OfferingBowlBlockReason.RespawnCooldownActive); } } internal static void NotifyOfferingBowlBlocked(OfferingBowl offeringBowl, Humanoid? user, OfferingBowlBlockResult result) { if (!((Object)(object)offeringBowl == (Object)null) && !((Object)(object)user == (Object)null) && result.Blocked) { ((Character)user).Message((MessageType)2, Localization.instance.Localize(offeringBowl.m_cantOfferText), 0, (Sprite)null); } } internal static void MarkOfferingBowlUsed(OfferingBowl? offeringBowl) { lock (Sync) { if ((Object)(object)offeringBowl == (Object)null || (Object)(object)ZNet.instance == (Object)null) { return; } OfferingBowlRuntimeState component = ((Component)offeringBowl).GetComponent<OfferingBowlRuntimeState>(); if ((Object)(object)component == (Object)null || component.RespawnMinutes <= 0f) { return; } long num = (component.LocalLastUseTicks = ZNet.instance.GetTime().Ticks); ZNetView componentInParent = ((Component)offeringBowl).GetComponentInParent<ZNetView>(); if (!((Object)(object)componentInParent == (Object)null) && componentInParent.IsValid()) { if (!componentInParent.IsOwner()) { componentInParent.ClaimOwnership(); } if (componentInParent.IsOwner()) { componentInParent.GetZDO().Set(OfferingBowlLastUseTicksKey, num); } } } } internal static void BeginOfferingBowlBossSpawnAttempt(OfferingBowl? offeringBowl, Vector3 spawnPoint) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)offeringBowl?.m_bossPrefab == (Object)null) { return; } lock (Sync) { string refundPayload = ConsumePreparedOfferingRefundPayload(offeringBowl); QueueOfferingBowlBossSpawnAttemptLocked(offeringBowl, spawnPoint, refundPayload, 0f, "started"); } } internal static void PrepareOfferingBowlRefundPayload(OfferingBowl? offeringBowl) { if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)offeringBowl == (Object)null) { return; } lock (Sync) { string text = BuildOfferingRefundPayload(offeringBowl); GetOrAddOfferingBowlRuntimeState(offeringBowl).PendingRefundPayload = text; BossRulesDebugLog.Client($"Altar refund prepared altar='{((Object)offeringBowl).name}' useItemStands={offeringBowl.m_useItemStands} payload='{FormatRefundPayloadForLog(text)}'."); } } internal static void PrepareAndQueueOfferingBowlRefundPayload(OfferingBowl? offeringBowl, Vector3 spawnPoint) { //IL_006c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)offeringBowl?.m_bossPrefab == (Object)null) { return; } lock (Sync) { string text = BuildOfferingRefundPayload(offeringBowl); GetOrAddOfferingBowlRuntimeState(offeringBowl).PendingRefundPayload = text; BossRulesDebugLog.Client($"Altar refund prepared altar='{((Object)offeringBowl).name}' useItemStands={offeringBowl.m_useItemStands} payload='{FormatRefundPayloadForLog(text)}'."); QueueOfferingBowlBossSpawnAttemptLocked(offeringBowl, spawnPoint, text, Math.Max(0f, offeringBowl.m_spawnBossDelay), "queued"); } } internal static void FinalizeOfferingBowlBossSpawnAttempt(OfferingBowl? offeringBowl, Vector3 spawnPoint) { if ((Object)(object)ZNet.instance == (Object)null) { return; } lock (Sync) { TryMarkNearbyPendingAltarSummonsLocked(); } } internal static void TryMarkAltarSummonedCharacter(Character? character) { if ((Object)(object)ZNet.instance == (Object)null || (Object)(object)((character != null) ? ((Component)character).gameObject : null) == (Object)null) { return; } ZNetView component = ((Component)character).GetComponent<ZNetView>(); if ((Object)(object)component == (Object)null || !component.IsValid()) { return; } lock (Sync) { TryMarkAltarSummonedCharacterLocked(character, component.GetZDO()); } } internal static string GetPrefabName(GameObject? gameObject) { if ((Object)(object)gameObject == (Object)null) { return ""; } ZNetView component = gameObject.GetComponent<ZNetView>(); ZDO val = ((component != null) ? component.GetZDO() : null); if (val != null && (Object)(object)ZNetScene.instance != (Object)null) { GameObject prefab = ZNetScene.instance.GetPrefab(val.GetPrefab()); if ((Object)(object)prefab != (Object)null) { return ((Object)prefab).name; } } string prefabName = Utils.GetPrefabName(gameObject); if (!string.IsNullOrWhiteSpace(prefabName)) { return prefabName; } return TrimCloneSuffix(((Object)gameObject).name); } private static void ReapplyRegisteredLocationsLocked() { BossRulesDebugLog.Client($"Altar reapply registered locations count={RegisteredLocationPrefabs.Count}."); foreach (KeyValuePair<Location, string> item in RegisteredLocationPrefabs.ToList()) { Location key = item.Key; string value = item.Value; if ((Object)(object)key == (Object)null) { RegisteredLocationPrefabs.Remove(item.Key); } else { ReconcileRootLocked(((Component)key).transform, value); } } } private static void ReapplyLoadedLooseOfferingBowlsLocked() { int num = 0; int num2 = 0; int num3 = 0; OfferingBowl[] array = Object.FindObjectsByType<OfferingBowl>((FindObjectsSortMode)0); foreach (OfferingBowl val in array) { num++; if (!((Object)(object)val == (Object)null) && !((Object)(object)((Component)val).GetComponentInParent<Location>(true) != (Object)null)) { if (AltarItemStandHoverInfoFormatter.TryResolveOfferingBowlContext(val, out string locationPrefab, out Transform root)) { num2++; ReconcileRootLocked(root, locationPrefab); } else { num3++; } } } BossRulesDebugLog.Client($"Altar reapply loose offering bowls scanned={num} applied={num2} unresolved={num3}."); } private static void ReapplyLoadedLooseItemStandsLocked() { int num = 0; int num2 = 0; ItemStand[] array = Object.FindObjectsByType<ItemStand>((FindObjectsSortMode)0); foreach (ItemStand itemStand in array) { num++; if (ReconcileLooseItemStandLocked(itemStand)) { num2++; } } BossRulesDebugLog.Client($"Altar reapply loose item stands scanned={num} applied={num2}."); } private static bool ReconcileLooseItemStandLocked(ItemStand? itemStand) { if ((Object)(object)itemStand == (Object)null || (Object)(object)((Component)itemStand).GetComponentInParent<Location>(true) != (Object)null) { return false; } if (!AltarItemStandHoverInfoFormatter.TryGetRelevantOfferingBowl(itemStand, out OfferingBowl offeringBowl) || (Object)(object)offeringBowl == (Object)null) { BossRulesDebugLog.Client("Altar loose itemStand skipped: offeringBowl unresolved stand=" + ((Object)itemStand).name + "."); return false; } Location componentInParent = ((Component)offeringBowl).GetComponentInParent<Location>(true); if ((Object)(object)componentInParent != (Object)null) { if (!RegisteredLocationPrefabs.TryGetValue(componentInParent, out string value) && AltarLocationResolver.TryResolveLocationPrefabName(componentInParent, out value)) { RegisteredLocationPrefabs[componentInParent] = value; } if (string.IsNullOrWhiteSpace(value)) { BossRulesDebugLog.Client("Altar loose itemStand skipped: location prefab unresolved stand=" + ((Object)itemStand).name + " bowl=" + ((Object)offeringBowl).name + "."); return false; } BossRulesDebugLog.Client("Altar loose itemStand reapplying location prefab=" + value + " stand=" + ((Object)itemStand).name + " bowl=" + ((Object)offeringBowl).name + "."); ReconcileRootLocked(((Component)componentInParent).transform, value); return true; } if (AltarItemStandHoverInfoFormatter.TryResolveOfferingBowlContext(offeringBowl, out string locationPrefab, out Transform root)) { BossRulesDebugLog.Client("Altar loose itemStand reapplying loose root prefab=" + locationPrefab + " stand=" + ((Object)itemStand).name + " bowl=" + ((Object)offeringBowl).name + "."); ReconcileRootLocked(root, locationPrefab); return true; } BossRulesDebugLog.Client("Altar loose itemStand skipped: context unresolved stand=" + ((Object)itemStand).name + " bowl=" + ((Object)offeringBowl).name + "."); return false; } private static void ReconcileRootLocked(Transform? root, string prefabName) { if ((Object)(object)root == (Object)null) { return; } string text = (prefabName ?? "").Trim(); if (text.Length == 0) { return; } if (!IsGameDataReady()) { _pendingGameDataReapply = true; BossRulesDebugLog.Client("Altar reconcile deferred prefab=" + text + " root=" + ((Object)root).name + ". " + DescribeGameDataState()); return; } OfferingBowl[] componentsInChildren = ((Component)root).GetComponentsInChildren<OfferingBowl>(true); ItemStand[] componentsInChildren2 = ((Component)root).GetComponentsInChildren<ItemStand>(true); CaptureSnapshots(componentsInChildren, componentsInChildren2); RestoreComponents(componentsInChildren, componentsInChildren2); if (!BossRulesConfig.IsAltarRulesEnabled() || !ActiveEntriesByPrefab.TryGetValue(text, out List<AltarConfigurationEntry> value)) { BossRulesDebugLog.Client($"Altar reconcile restore-only prefab={text} root={((Object)root).name} enabled={BossRulesConfig.IsAltarRulesEnabled()} configured={ActiveEntriesByPrefab.ContainsKey(text)} bowls={componentsInChildren.Length} childItemStands={componentsInChildren2.Length}."); return; } OfferingBowl val = ((IEnumerable<OfferingBowl>)componentsInChildren).FirstOrDefault((Func<OfferingBowl, bool>)((OfferingBowl bowl) => (Object)(object)bowl != (Object)null)); Dictionary<string, ItemStand> dictionary = BuildItemStandLookup(root, componentsInChildren2); BossRulesDebugLog.Client(string.Format("Altar reconcile applying prefab={0} root={1} entries={2} bowls={3} childItemStands={4} childPaths={5} offeringBowl={6}.", text, ((Object)root).name, value.Count, componentsInChildren.Length, componentsInChildren2.Length, dictionary.Count, ((Object)(object)val != (Object)null) ? ((Object)val).name : "<none>")); foreach (AltarConfigurationEntry item in value) { if (item.OfferingBowl != null && (Object)(object)val != (Object)null) { ApplyOfferingBowl(val, item.OfferingBowl, text); } List<AltarItemStandDefinition> itemStands = item.ItemStands; if (itemStands != null && itemStands.Count > 0) { List<ItemStand> relevantItemStands = GetRelevantItemStands(val, componentsInChildren2); ApplyConfiguredItemStands(item.ItemStands, relevantItemStands, dictionary, text, root, val); } } } private static bool IsGameDataReady() { if ((Object)(object)ZoneSystem.instance != (Object)null) { List<GameObject> list = ObjectDB.instance?.m_items; if (list != null && list.Count > 0) { list = ZNetScene.instance?.m_prefabs; if (list != null) { return list.Count > 0; } return false; } } return false; } private static string DescribeGameDataState() { string arg = (((Object)(object)ZoneSystem.instance != (Object)null) ? "ready" : "null"); int num = ObjectDB.instance?.m_items?.Count ?? (-1); int num2 = ZNetScene.instance?.m_prefabs?.Count ?? (-1); return $"ZoneSystem={arg} ObjectDB.items={num} ZNetScene.prefabs={num2}"; } private static void CaptureSnapshots(IEnumerable<OfferingBowl> offeringBowls, IEnumerable<ItemStand> itemStands) { foreach (OfferingBowl offeringBowl in offeringBowls) { if (!((Object)(object)offeringBowl == (Object)null)) { OfferingBowlRuntimeState orAddOfferingBowlRuntimeState = GetOrAddOfferingBowlRuntimeState(offeringBowl); if (orAddOfferingBowlRuntimeState.Snapshot == null) { OfferingBowlSnapshot offeringBowlSnapshot = (orAddOfferingBowlRuntimeState.Snapshot = CaptureOfferingBowlSnapshot(offeringBowl)); } } } foreach (ItemStand itemStand in itemStands) { if (!((Object)(object)itemStand == (Object)null)) { ItemStandRuntimeState orAddItemStandRuntimeState = GetOrAddItemStandRuntimeState(itemStand); if (orAddItemStandRuntimeState.Snapshot == null) { ItemStandSnapshot itemStandSnapshot = (orAddItemStandRuntimeState.Snapshot = CaptureItemStandSnapshot(itemStand)); } } } } private static void RestoreRoot(Transform root) { RestoreComponents(((Component)root).GetComponentsInChildren<OfferingBowl>(true), ((Component)root).GetComponentsInChildren<ItemStand>(true)); } private static void RestoreComponents(IEnumerable<OfferingBowl> offeringBowls, IEnumerable<ItemStand> itemStands) { foreach (OfferingBowl offeringBowl in offeringBowls) { OfferingBowlRuntimeState offeringBowlRuntimeState = (((Object)(object)offeringBowl != (Object)null) ? ((Component)offeringBowl).GetComponent<OfferingBowlRuntimeState>() : null); if (offeringBowlRuntimeState?.Snapshot != null && offeringBowlRuntimeState.Applied) { RestoreOfferingBowl(offeringBowl, offeringBowlRuntimeState.Snapshot); offeringBowlRuntimeState.Applied = false; offeringBowlRuntimeState.RespawnMinutes = 0f; } } foreach (ItemStand itemStand in itemStands) { ItemStandRuntimeState itemStandRuntimeState = (((Object)(object)itemStand != (Object)null) ? ((Component)itemStand).GetComponent<ItemStandRuntimeState>() : null); if (itemStandRuntimeState?.Snapshot != null && itemStandRuntimeState.Applied) { RestoreItemStand(itemStand, itemStandRuntimeState.Snapshot); itemStandRuntimeState.Applied = false; } } } private static void ApplyOfferingBowl(OfferingBowl offeringBowl, AltarOfferingBowlDefinition entry, string prefabName) { string text = prefabName + "@offeringBowl"; if (entry.BossItem != null) { offeringBowl.m_bossItem = ResolveItemDrop(entry.BossItem, text + "/bossItem"); } if (entry.BossItems.HasValue) { offeringBowl.m_bossItems = Math.Max(1, entry.BossItems.Value); } if (entry.BossPrefab != null) { offeringBowl.m_bossPrefab = ResolveSpawnPrefab(entry.BossPrefab, text + "/bossPrefab"); } if (entry.ItemPrefab != null) { offeringBowl.m_itemPrefab = ResolveItemDrop(entry.ItemPrefab, text + "/itemPrefab"); } if (entry.SetGlobalKey != null) { offeringBowl.m_setGlobalKey = entry.SetGlobalKey; } if (entry.RenderSpawnAreaGizmos.HasValue) { offeringBowl.m_renderSpawnAreaGizmos = entry.RenderSpawnAreaGizmos.Value; } if (entry.AlertOnSpawn.HasValue) { offeringBowl.m_alertOnSpawn = entry.AlertOnSpawn.Value; } if (entry.SpawnBossDelay.HasValue) { offeringBowl.m_spawnBossDelay = Mathf.Max(0f, entry.SpawnBossDelay.Value); } float? num = entry.SpawnBossDistance?.Min; if (num.HasValue) { float valueOrDefault = num.GetValueOrDefault(); offeringBowl.m_spawnBossMinDistance = Mathf.Max(0f, valueOrDefault); } num = entry.SpawnBossDistance?.Max; if (num.HasValue) { float valueOrDefault2 = num.GetValueOrDefault(); offeringBowl.m_spawnBossMaxDistance = Mathf.Max(0f, valueOrDefault2); } if (entry.SpawnBossMaxYDistance.HasValue) { offeringBowl.m_spawnBossMaxYDistance = Mathf.Max(0f, entry.SpawnBossMaxYDistance.Value); } if (entry.GetSolidHeightMargin.HasValue) { offeringBowl.m_getSolidHeightMargin = Math.Max(0, entry.GetSolidHeightMargin.Value); } if (entry.EnableSolidHeightCheck.HasValue) { offeringBowl.m_enableSolidHeightCheck = entry.EnableSolidHeightCheck.Value; } if (entry.SpawnPointClearingRadius.HasValue) { offeringBowl.m_spawnPointClearingRadius = Mathf.Max(0f, entry.SpawnPointClearingRadius.Value); } if (entry.SpawnYOffset.HasValue) { offeringBowl.m_spawnYOffset = entry.SpawnYOffset.Value; } if (entry.UseItemStands.HasValue) { offeringBowl.m_useItemStands = entry.UseItemStands.Value; } if (entry.ItemStandPrefix != null) { offeringBowl.m_itemStandPrefix = entry.ItemStandPrefix; } if (entry.ItemStandMaxRange.HasValue) { offeringBowl.m_itemstandMaxRange = Mathf.Max(0f, entry.ItemStandMaxRange.Value); } OfferingBowlRuntimeState orAddOfferingBowlRuntimeState = GetOrAddOfferingBowlRuntimeState(offeringBowl); orAddOfferingBowlRuntimeState.Applied = true; orAddOfferingBowlRuntimeState.RespawnMinutes = (entry.RespawnMinutes.HasValue ? Mathf.Max(0f, entry.RespawnMinutes.Value) : 0f); BossRulesDebugLog.Client(string.Format("Altar offeringBowl applied context={0} bossPrefab={1} useItemStands={2} prefix='{3}' maxRange={4:0.##} respawnMinutes={5:0.##}.", text, ((Object)(object)offeringBowl.m_bossPrefab != (Object)null) ? GetPrefabName(offeringBowl.m_bossPrefab) : "<null>", offeringBowl.m_useItemStands, offeringBowl.m_itemStandPrefix, offeringBowl.m_itemstandMaxRange, orAddOfferingBowlRuntimeState.RespawnMinutes)); } private static void ApplyConfiguredItemStands(IReadOnlyList<AltarItemStandDefinition> definitions, IReadOnlyList<ItemStand> relevantItemStands, Dictionary<string, ItemStand> childItemStandsByPath, string prefabName, Transform root, OfferingBowl? offeringBowl) { HashSet<int> exactMatchedItemStandIds = new HashSet<int>(); List<AltarItemStandDefinition> list = new List<AltarItemStandDefinition>(); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (AltarItemStandDefinition definition in definitions) { string text = (definition.Path ?? "").Trim(); if (text.Length == 0) { foreach (ItemStand relevantItemStand in relevantItemStands) { ApplyItemStand(relevantItemStand, definition, prefabName, root); } continue; } list.Add(definition); if (childItemStandsByPath.TryGetValue(text, out ItemStand value)) { exactMatchedItemStandIds.Add(((Object)value).GetInstanceID()); CaptureAuthoredItemStandSlot(prefabName, text, value, offeringBowl); BossRulesDebugLog.Client("Altar itemStand exact path match prefab=" + prefabName + " path='" + text + "' stand=" + ((Object)value).name + "."); ApplyItemStand(value, definition, prefabName, root); } else { hashSet.Add(text); } } if ((Object)(object)offeringBowl == (Object)null || list.Count == 0) { foreach (string item in hashSet) { WarnInvalidEntry("Entry '" + prefabName + "@itemStands[" + item + "]' references a missing ItemStand path."); } return; } List<ItemStand> list2 = relevantItemStands.Where((ItemStand itemStand) => (Object)(object)itemStand != (Object)null && !exactMatchedItemStandIds.Contains(((Object)itemStand).GetInstanceID())).ToList(); if (list2.Count == 0) { foreach (string item2 in hashSet) { WarnInvalidEntry("Entry '" + prefabName + "@itemStands[" + item2 + "]' references a missing ItemStand path."); } return; } foreach (ItemStand item3 in list2) { LooseItemStandAuthoredPathsByInstance.Remove(item3); } TryStampLooseItemStandAuthoredPaths(offeringBowl, prefabName, list2); foreach (AltarItemStandDefinition item4 in list) { string path = (item4.Path ?? "").Trim(); string value2; ItemStand val = ((IEnumerable<ItemStand>)list2).FirstOrDefault((Func<ItemStand, bool>)((ItemStand itemStand) => LooseItemStandAuthoredPathsByInstance.TryGetValue(itemStand, out value2) && string.Equals(value2, path, StringComparison.Ordinal))); if ((Object)(object)val == (Object)null) { if (hashSet.Contains(path)) { WarnInvalidEntry("Entry '" + prefabName + "@itemStands[" + path + "]' references a missing ItemStand path."); } } else { hashSet.Remove(path); BossRulesDebugLog.Client("Altar itemStand authored path remap prefab=" + prefabName + " path='" + path + "' stand=" + ((Object)val).name + "."); ApplyItemStand(val, item4, prefabName, root); } } } private static void ApplyItemStand(ItemStand itemStand, AltarItemStandDefinition entry, string prefabName, Transform root) { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) string text = (string.IsNullOrWhiteSpace(entry.Path) ? (prefabName + "@itemStands") : (prefabName + "@itemStands[" + entry.Path + "]")); List<ItemDrop> list = null; ItemStandRuntimeState orAddItemStandRuntimeState = GetOrAddItemStandRuntimeState(itemStand); ItemStandRuntimeState itemStandRuntimeState = orAddItemStandRuntimeState; if (itemStandRuntimeState.Snapshot == null) { ItemStandSnapshot itemStandSnapshot = (itemStandRuntimeState.Snapshot = CaptureItemStandSnapshot(itemStand)); } if (entry.CanBeRemoved.HasValue) { itemStand.m_canBeRemoved = entry.CanBeRemoved.Value; } if (entry.AutoAttach.HasValue) { itemStand.m_autoAttach = entry.AutoAttach.Value; } if (entry.OrientationType != null && Enum.TryParse<Orientation>(entry.OrientationType, ignoreCase: true, out Orientation result)) { itemStand.m_orientationType = result; } if (entry.SupportedTypes != null) { itemStand.m_supportedTypes = ResolveItemStandTypes(entry.SupportedTypes, text + "/supportedTypes"); } if (entry.SupportedItems != null) { list = (itemStand.m_supportedItems = ResolveItemDropList(entry.SupportedItems, text + "/supportedItems")); BossRulesDebugLog.Client("Altar itemStand supportedItems resolved context=" + text + " requested=[" + FormatNames(entry.SupportedItems) + "] resolved=[" + FormatItemDrops(list) + "]."); } if (entry.UnsupportedItems != null) { itemStand.m_unsupportedItems = ResolveItemDropList(entry.UnsupportedItems, text + "/unsupportedItems"); BossRulesDebugLog.Client("Altar itemStand unsupportedItems resolved context=" + text + " requested=[" + FormatNames(entry.UnsupportedItems) + "] resolved=[" + FormatItemDrops(itemStand.m_unsupportedItems) + "]."); } else if (list != null) { RemoveSupportedItemsFromUnsupportedList(itemStand, list); } if (entry.PowerActivationDelay.HasValue) { itemStand.m_powerActivationDelay = Mathf.Max(0f, entry.PowerActivationDelay.Value); } if (entry.GuardianPower != null) { itemStand.m_guardianPower = ResolveStatusEffect(entry.GuardianPower, text + "/guardianPower"); } orAddItemStandRuntimeState.Applied = true; BossRulesDebugLog.Client($"Altar itemStand applied context={text} stand={((Object)itemStand).name} path='{GetRelativePath(root, ((Component)itemStand).transform)}' autoAttach={itemStand.m_autoAttach} supported=[{FormatItemDrops(itemStand.m_supportedItems)}] unsupported=[{FormatItemDrops(itemStand.m_unsupportedItems)}] applied={orAddItemStandRuntimeState.Applied}."); } private static List<ItemStand> GetRelevantItemStands(OfferingBowl? offeringBowl, IEnumerable<ItemStand> childItemStands) { List<ItemStand> list = new List<ItemStand>(); HashSet<int> hashSet = new HashSet<int>(); foreach (ItemStand childItemStand in childItemStands) { if ((Object)(object)childItemStand != (Object)null && hashSet.Add(((Object)childItemStand).GetInstanceID())) { list.Add(childItemStand); } } if ((Object)(object)offeringBowl == (Object)null || !offeringBowl.m_useItemStands) { return list; } foreach (ItemStand item in AltarItemStandHoverInfoFormatter.FindRelevantItemStands(offeringBowl)) { if ((Object)(object)item != (Object)null && hashSet.Add(((Object)item).GetInstanceID())) { list.Add(item); } } return list; } private static Dictionary<string, ItemStand> BuildItemStandLookup(Transform root, IEnumerable<ItemStand> itemStands) { Dictionary<string, ItemStand> dictionary = new Dictionary<string, ItemStand>(StringComparer.Ordinal); foreach (ItemStand itemStand in itemStands) { if ((Object)(object)itemStand != (Object)null) { dictionary[GetRelativePath(root, ((Component)itemStand).transform)] = itemStand; } } return dictionary; } private static void CaptureAuthoredItemStandSlot(string prefabName, string configuredPath, ItemStand itemStand, OfferingBowl? offeringBowl) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)offeringBowl == (Object)null || (Object)(object)itemStand == (Object)null) { return; } string text = (prefabName ?? "").Trim(); string path = (configuredPath ?? "").Trim(); if (text.Length != 0 && path.Length != 0) { if (!AuthoredItemStandSlotsByPrefab.TryGetValue(text, out List<AuthoredItemStandSlotTemplate> value)) { value = new List<AuthoredItemStandSlotTemplate>(); AuthoredItemStandSlotsByPrefab[text] = value; } Vector3 offeringBowlLocalOffset = ((Component)offeringBowl).transform.InverseTransformPoint(((Component)itemStand).transform.position); int num = value.FindIndex((AuthoredItemStandSlotTemplate slot) => string.Equals(slot.Path, path, StringComparison.Ordinal)); AuthoredItemStandSlotTemplate authoredItemStandSlotTemplate = new AuthoredItemStandSlotTemplate { Path = path, OfferingBowlLocalOffset = offeringBowlLocalOffset }; if (num >= 0) { value[num] = authoredItemStandSlotTemplate; } else { value.Add(authoredItemStandSlotTemplate); } } } private static void TryStampLooseItemStandAuthoredPaths(OfferingBowl offeringBowl, string prefabName, IReadOnlyList<ItemStand> relevantItemStands) { //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) CleanupLooseItemStandAuthoredPaths(); string text = (prefabName ?? "").Trim(); if ((Object)(object)offeringBowl == (Object)null || text.Length == 0 || !AuthoredItemStandSlotsByPrefab.TryGetValue(text, out List<AuthoredItemStandSlotTemplate> value) || value.Count == 0 || relevantItemStands.Count == 0) { BossRulesDebugLog.Client($"Altar authored path remap skipped prefab={text} templates={(AuthoredItemStandSlotsByPrefab.TryGetValue(text, out List<AuthoredItemStandSlotTemplate> value2) ? value2.Count : 0)} relevant={relevantItemStands.Count}."); return; } HashSet<int> hashSet = new HashSet<int>(); HashSet<string> hashSet2 = new HashSet<string>(StringComparer.Ordinal); foreach (ItemStand relevantItemStand in relevantItemStands) { if (!((Object)(object)relevantItemStand == (Object)null) && LooseItemStandAuthoredPathsByInstance.TryGetValue(relevantItemStand, out string assignedPath) && !string.IsNullOrWhiteSpace(assignedPath) && value.Any((AuthoredItemStandSlotTemplate template) => string.Equals(template.Path, assignedPath, StringComparison.Ordinal))) { hashSet.Add(((Object)relevantItemStand).GetInstanceID()); hashSet2.Add(assignedPath); } } List<(float, ItemStand, AuthoredItemStandSlotTemplate)> list = new List<(float, ItemStand, AuthoredItemStandSlotTemplate)>(); foreach (ItemStand relevantItemStand2 in relevantItemStands) { if ((Object)(object)relevantItemStand2 == (Object)null || hashSet.Contains(((Object)relevantItemStand2).GetInstanceID())) { continue; } Vector3 val = ((Component)offeringBowl).transform.InverseTransformPoint(((Component)relevantItemStand2).transform.position); foreach (AuthoredItemStandSlotTemplate item4 in value) { if (!hashSet2.Contains(item4.Path)) { float item = Vector3.SqrMagnitude(val - item4.OfferingBowlLocalOffset); list.Add((item, relevantItemStand2, item4)); } } } BossRulesDebugLog.Client($"Altar authored path remap candidates prefab={text} templates={value.Count} relevant={relevantItemStands.Count} candidates={list.Count}."); list.Sort(((float Distance, ItemStand ItemStand, AuthoredItemStandSlotTemplate Template) left, (float Distance, ItemStand ItemStand, AuthoredItemStandSlotTemplate Template) right) => left.Distance.CompareTo(right.Distance)); foreach (var item5 in list) { ItemStand item2 = item5.Item2; AuthoredItemStandSlotTemplate item3 = item5.Item3; int instanceID = ((Object)item2).GetInstanceID(); if (!hashSet.Contains(instanceID) && !hashSet2.Contains(item3.Path)) { LooseItemStandAuthoredPathsByInstance[item2] = item3.Path; hashSet.Add(instanceID); hashSet2.Add(item3.Path); BossRulesDebugLog.Client("Altar authored path assigned prefab=" + text + " path='" + item3.Path + "' stand=" + ((Object)item2).name + "."); } } } private static void CleanupLooseItemStandAuthoredPaths() { List<ItemStand> list = null; foreach (ItemStand key in LooseItemStandAuthoredPathsByInstance.Keys) { if (!((Object)(object)key != (Object)null) || !((Object)(object)((Component)key).gameObject != (Object)null)) { if (list == null) { list = new List<ItemStand>(); } list.Add(key); } } if (list == null) { return; } foreach (ItemStand item in list) { LooseItemStandAuthoredPathsByInstance.Remove(item); } } internal static OfferingBowlSnapshot CaptureOfferingBowlSnapshot(OfferingBowl offeringBowl) { return new OfferingBowlSnapshot { BossItem = (NormalizeReferencePrefabName(((Object)(object)offeringBowl.m_bossItem != (Object)null) ? ((Component)offeringBowl.m_bossItem).gameObject : null) ?? ""), BossItems = offeringBowl.m_bossItems, BossPrefab = (NormalizeReferencePrefabName(offeringBowl.m_bossPrefab) ?? ""), ItemPrefab = (NormalizeReferencePrefabName(((Object)(object)offeringBowl.m_itemPrefab != (Object)null) ? ((Component)offeringBowl.m_itemPrefab).gameObject : null) ?? ""), SetGlobalKey = offeringBowl.m_setGlobalKey, RenderSpawnAreaGizmos = offeringBowl.m_renderSpawnAreaGizmos, AlertOnSpawn = offeringBowl.m_alertOnSpawn, SpawnBossDelay = offeringBowl.m_spawnBossDelay, SpawnBossMaxDistance = offeringBowl.m_spawnBossMaxDistance, SpawnBossMinDistance = offeringBowl.m_spawnBossMinDistance, SpawnBossMaxYDistance = offeringBowl.m_spawnBossMaxYDistance, GetSolidHeightMargin = offeringBowl.m_getSolidHeightMargin, EnableSolidHeightCheck = offeringBowl.m_enableSolidHeightCheck, SpawnPointClearingRadius = offeringBowl.m_spawnPointClearingRadius, SpawnYOffset = offeringBowl.m_spawnYOffset, UseItemStands = offeringBowl.m_useItemStands, ItemStandPrefix = offeringBowl.m_itemStandPrefix, ItemStandMaxRange = offeringBowl.m_itemstandMaxRange }; } internal unsafe static ItemStandSnapshot CaptureItemStandSnapshot(ItemStand itemStand) { return new ItemStandSnapshot { CanBeRemoved = itemStand.m_canBeRemoved, AutoAttach = itemStand.m_autoAttach, OrientationType = ((object)Unsafe.As<Orientation, Orientation>(ref itemStand.m_orientationType)/*cast due to .constrained prefix*/).ToString(), SupportedTypes = itemStand.m_supportedTypes.Select((ItemType type) => ((object)(*(ItemType*)(&type))/*cast due to .constrained prefix*/).ToString()).ToList(), SupportedItems = (from item in