Decompiled source of BossRules v1.0.0

BossRules.dll

Decompiled a day ago
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