Decompiled source of IntelligentAutoHideHUD v0.1.0

HideHudSilksong.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using TMPro;
using TMProOld;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyCompany("HideHudSilksong")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.1.0.0")]
[assembly: AssemblyInformationalVersion("0.1.0+eb790eac532ff9cc72aa6108da5e6f3df564fbc5")]
[assembly: AssemblyProduct("HideHudSilksong")]
[assembly: AssemblyTitle("HideHudSilksong")]
[assembly: AssemblyVersion("0.1.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace HideHudSilksong
{
	public sealed class CombatSensor
	{
		private const int TerrainLayerMask = 256;

		private static readonly FieldInfo BattleStartedField = typeof(BattleScene).GetField("started", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo BattleCompletedField = typeof(BattleScene).GetField("completed", BindingFlags.Instance | BindingFlags.NonPublic);

		private readonly PluginConfig cfg;

		private readonly HashSet<HealthManager> damageSubscriptions = new HashSet<HealthManager>();

		private readonly HashSet<HealthManager> confirmedCombatants = new HashSet<HealthManager>();

		private BattleScene[] battleScenes = (BattleScene[])(object)new BattleScene[0];

		private float nextBattleSceneRefresh;

		private float nextScan;

		private float lastSignalTime = float.NegativeInfinity;

		private bool enemiesNear;

		private bool arenaActive;

		public bool InCombat { get; private set; }

		public bool ArenaActive => arenaActive;

		public CombatSensor(PluginConfig cfg)
		{
			this.cfg = cfg;
		}

		public void NotifySignal()
		{
			lastSignalTime = Time.time;
		}

		public void OnSceneChanged()
		{
			battleScenes = (BattleScene[])(object)new BattleScene[0];
			nextBattleSceneRefresh = 0f;
			nextScan = 0f;
			damageSubscriptions.Clear();
			confirmedCombatants.Clear();
		}

		public void Tick(HeroController hero)
		{
			float time = Time.time;
			if (time >= nextBattleSceneRefresh)
			{
				battleScenes = Object.FindObjectsByType<BattleScene>((FindObjectsInactive)1, (FindObjectsSortMode)0);
				nextBattleSceneRefresh = time + 5f;
			}
			if (time >= nextScan)
			{
				Scan(hero, time);
				nextScan = time + Mathf.Max(0.05f, cfg.EnemyScanInterval.Value);
			}
			if (enemiesNear || arenaActive)
			{
				lastSignalTime = time;
			}
			bool inCombat = InCombat;
			InCombat = enemiesNear || arenaActive || time - lastSignalTime <= cfg.CombatExitLinger.Value;
			if (cfg.LogCombatTriggers.Value)
			{
				if (!inCombat && InCombat && !enemiesNear && !arenaActive)
				{
					HideHudPlugin.Log.LogInfo((object)$"Combat visibility started (signal: took-damage linger, {cfg.CombatExitLinger.Value - (time - lastSignalTime):F1}s left).");
				}
				else if (inCombat && !InCombat)
				{
					HideHudPlugin.Log.LogInfo((object)"Combat visibility ended.");
				}
			}
		}

		private void Scan(HeroController hero, float now)
		{
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_017d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0182: Unknown result type (might be due to invalid IL or missing references)
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_018b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01df: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_021b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0360: Unknown result type (might be due to invalid IL or missing references)
			//IL_0365: Unknown result type (might be due to invalid IL or missing references)
			//IL_036a: Unknown result type (might be due to invalid IL or missing references)
			//IL_036c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0371: Unknown result type (might be due to invalid IL or missing references)
			bool flag = enemiesNear;
			bool flag2 = arenaActive;
			string text = null;
			enemiesNear = false;
			Vector3 position = ((Component)hero).transform.position;
			Vector2 val = Vector2.op_Implicit(position);
			float num = cfg.ProximityRadius.Value * cfg.ProximityRadius.Value;
			bool value = cfg.UseAggroDetection.Value;
			float num2 = cfg.AggroRadius.Value * cfg.AggroRadius.Value;
			float value2 = cfg.MaxEnemyZOffset.Value;
			bool value3 = cfg.RequireThreatCapability.Value;
			bool value4 = cfg.RequireLineOfSight.Value;
			bool value5 = cfg.RequireOnScreen.Value;
			float value6 = cfg.OnScreenPadding.Value;
			GameCameras val2 = (value5 ? GameCameras.SilentInstance : null);
			Camera val3 = (Object.op_Implicit((Object)(object)val2) ? val2.mainCamera : null);
			Vector2 val4;
			RaycastHit2D val5 = default(RaycastHit2D);
			foreach (HealthManager item in HealthManager.EnumerateActiveEnemies())
			{
				if (!Object.op_Implicit((Object)(object)item))
				{
					continue;
				}
				if (damageSubscriptions.Add(item))
				{
					HealthManager confirmedHm = item;
					item.TookDamage += delegate
					{
						confirmedCombatants.Add(confirmedHm);
						NotifySignal();
					};
				}
				if (item.isDead || item.hp <= 0)
				{
					continue;
				}
				Vector3 position2 = ((Component)item).transform.position;
				if (Mathf.Abs(position2.z - position.z) > value2)
				{
					continue;
				}
				AlertRange componentInChildren = ((Component)item).GetComponentInChildren<AlertRange>();
				if (!value3 || confirmedCombatants.Contains(item) || !((Object)(object)componentInChildren == (Object)null) || !((Object)(object)((Component)item).GetComponentInChildren<DamageHero>() == (Object)null))
				{
					val4 = Vector2.op_Implicit(position2) - val;
					float sqrMagnitude = ((Vector2)(ref val4)).sqrMagnitude;
					if (sqrMagnitude <= num && (!value4 || !Helper.LineCast2DHit(val, Vector2.op_Implicit(position2), 256, ref val5)) && (!Object.op_Implicit((Object)(object)val3) || IsOnScreen(val3, position2, value6)))
					{
						enemiesNear = true;
						text = $"proximity: {((Object)item).name} at {Mathf.Sqrt(sqrMagnitude):F1}";
						break;
					}
					if (value && sqrMagnitude <= num2 && (Object)(object)componentInChildren != (Object)null && componentInChildren.IsHeroInRange())
					{
						enemiesNear = true;
						text = $"aggro: {((Object)item).name} at {Mathf.Sqrt(sqrMagnitude):F1}";
						break;
					}
				}
			}
			arenaActive = false;
			if (cfg.HoldDuringArenas.Value && BossSceneController.IsBossScene)
			{
				arenaActive = true;
				text = "boss scene";
			}
			if (cfg.HoldDuringArenas.Value && BattleStartedField != null && BattleCompletedField != null)
			{
				float num3 = cfg.ArenaMaxDistance.Value * cfg.ArenaMaxDistance.Value;
				BattleScene[] array = battleScenes;
				foreach (BattleScene val6 in array)
				{
					if (!Object.op_Implicit((Object)(object)val6) || !((Behaviour)val6).isActiveAndEnabled)
					{
						continue;
					}
					val4 = Vector2.op_Implicit(((Component)val6).transform.position) - val;
					if (((Vector2)(ref val4)).sqrMagnitude <= num3 && (bool)BattleStartedField.GetValue(val6) && !(bool)BattleCompletedField.GetValue(val6))
					{
						arenaActive = true;
						if (text == null)
						{
							text = "arena: " + ((Object)val6).name;
						}
						break;
					}
				}
			}
			if (cfg.LogCombatTriggers.Value && ((enemiesNear && !flag) || (arenaActive && !flag2)))
			{
				HideHudPlugin.Log.LogInfo((object)("Combat visibility started (" + (text ?? "unknown") + ")."));
			}
		}

		private static bool IsOnScreen(Camera cam, Vector3 worldPos, float padding)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			Vector3 val = cam.WorldToViewportPoint(worldPos);
			if (val.z > 0f && val.x >= 0f - padding && val.x <= 1f + padding && val.y >= 0f - padding)
			{
				return val.y <= 1f + padding;
			}
			return false;
		}
	}
	[BepInPlugin("com.scrimas.hidehudsilksong", "Intelligent Auto-Hiding HUD", "0.1.0")]
	[DefaultExecutionOrder(30000)]
	public sealed class HideHudPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.scrimas.hidehudsilksong";

		public const string PluginName = "Intelligent Auto-Hiding HUD";

		public const string PluginVersion = "0.1.0";

		internal static ManualLogSource Log;

		private PluginConfig cfg;

		private HudElementRegistry registry;

		private CombatSensor combat;

		private HealthSensor healthSensor;

		private SilkSensor silkSensor;

		private ToolAmmoSensor toolSensor;

		private HeroController hookedHero;

		private bool forceShow;

		private bool wasFrozen;

		private void Awake()
		{
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c7: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			cfg = new PluginConfig(((BaseUnityPlugin)this).Config);
			registry = new HudElementRegistry(((BaseUnityPlugin)this).Logger);
			combat = new CombatSensor(cfg);
			healthSensor = new HealthSensor(cfg.Health);
			silkSensor = new SilkSensor(cfg.Silk);
			toolSensor = new ToolAmmoSensor(cfg.Tools, registry);
			SceneManager.activeSceneChanged += OnActiveSceneChanged;
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Intelligent Auto-Hiding HUD 0.1.0 loaded. " + $"ForceShow={cfg.ForceShowKey.Value}, Dump={cfg.DumpHierarchyKey.Value}"));
		}

		private void OnDestroy()
		{
			SceneManager.activeSceneChanged -= OnActiveSceneChanged;
			UnhookHero();
			registry?.Shutdown();
			RestoreAll();
		}

		private void OnActiveSceneChanged(Scene from, Scene to)
		{
			combat.OnSceneChanged();
			registry.Invalidate();
		}

		private void OnHeroTookDamage()
		{
			combat.NotifySignal();
			healthSensor.ForceReveal();
		}

		private void HookHero(HeroController hero)
		{
			if (hookedHero != hero)
			{
				UnhookHero();
				hero.OnTakenDamage += OnHeroTookDamage;
				hookedHero = hero;
			}
		}

		private void UnhookHero()
		{
			if ((Object)(object)hookedHero != (Object)null)
			{
				hookedHero.OnTakenDamage -= OnHeroTookDamage;
				hookedHero = null;
			}
		}

		private void RestoreAll()
		{
			if (registry == null)
			{
				return;
			}
			foreach (HudElement item in registry.AllElements())
			{
				item.RestoreNow();
			}
		}

		private void Update()
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			KeyboardShortcut value = cfg.ForceShowKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				forceShow = !forceShow;
				((BaseUnityPlugin)this).Logger.LogInfo((object)$"Force-show HUD: {forceShow}");
			}
			value = cfg.DumpHierarchyKey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				HudHierarchyDumper.Dump(registry.HudRoot, ((BaseUnityPlugin)this).Logger);
			}
		}

		private void LateUpdate()
		{
			HeroController unsafeInstance = HeroController.UnsafeInstance;
			GameCameras silentInstance = GameCameras.SilentInstance;
			if (!cfg.Enabled.Value || !((Object)(object)unsafeInstance != (Object)null) || !((Object)(object)silentInstance != (Object)null))
			{
				SetAllVisible();
				TickFades();
				wasFrozen = false;
				return;
			}
			GameManager instance = GameManager.instance;
			PlayerData instance2 = PlayerData.instance;
			bool flag = ((Object)(object)instance != (Object)null && instance.isPaused) || (instance2?.isInventoryOpen ?? false);
			if (!silentInstance.IsHudVisible || flag)
			{
				wasFrozen = true;
				return;
			}
			if (wasFrozen)
			{
				healthSensor.AbsorbChangesFor(1f);
				silkSensor.AbsorbChangesFor(1f);
				toolSensor.AbsorbChangesFor(1f);
			}
			HookHero(unsafeInstance);
			registry.Tick();
			combat.Tick(unsafeInstance);
			healthSensor.Tick();
			silkSensor.Tick();
			toolSensor.Tick();
			bool flag2 = forceShow || combat.InCombat;
			float value = cfg.HiddenAlpha.Value;
			registry.Health.SetVisible(!cfg.Health.Manage.Value || flag2 || healthSensor.ShouldShow, value);
			registry.Silk.SetVisible(!cfg.Silk.Manage.Value || flag2 || silkSensor.ShouldShow, value);
			registry.Tools.SetVisible(!cfg.Tools.Manage.Value || flag2 || toolSensor.ShouldShow, value);
			registry.Others.SetVisible(!cfg.ManageUnknownElements.Value || flag2, value);
			if (wasFrozen)
			{
				wasFrozen = false;
				{
					foreach (HudElement item in registry.AllElements())
					{
						item.SnapNow();
					}
					return;
				}
			}
			TickFades();
		}

		private void SetAllVisible()
		{
			foreach (HudElement item in registry.AllElements())
			{
				item.SetVisible(visible: true, cfg.HiddenAlpha.Value);
			}
		}

		private void TickFades()
		{
			float value = cfg.FadeInSeconds.Value;
			float value2 = cfg.FadeOutSeconds.Value;
			foreach (HudElement item in registry.AllElements())
			{
				item.Tick(value, value2);
			}
		}
	}
	public sealed class HudElement
	{
		private enum Kind : byte
		{
			Sprite,
			Tk2dSprite,
			Tk2dText,
			Graphic,
			Tmp,
			TmpOld
		}

		private struct Entry
		{
			public Component Component;

			public Kind Kind;

			public float BaseAlpha;

			public float LastApplied;
		}

		private const float RescanInterval = 0.35f;

		public readonly string Name;

		private readonly List<Transform> roots = new List<Transform>();

		private readonly List<Transform> excluded = new List<Transform>();

		private readonly List<Entry> entries = new List<Entry>();

		private readonly Dictionary<Component, Entry> carryOver = new Dictionary<Component, Entry>();

		private readonly HashSet<Component> seen = new HashSet<Component>();

		private float current = 1f;

		private float target = 1f;

		private float nextRescan;

		private bool touching;

		public bool HasRoots => roots.Count > 0;

		public IEnumerable<Transform> Roots => roots;

		public HudElement(string name)
		{
			Name = name;
		}

		public bool RootsAlive()
		{
			if (roots.Count == 0)
			{
				return false;
			}
			for (int i = 0; i < roots.Count; i++)
			{
				if (!Object.op_Implicit((Object)(object)roots[i]))
				{
					return false;
				}
			}
			return true;
		}

		public void SetRoots(IEnumerable<Transform> newRoots, IEnumerable<Transform> newExcluded = null)
		{
			RestoreNow();
			roots.Clear();
			excluded.Clear();
			foreach (Transform newRoot in newRoots)
			{
				if (Object.op_Implicit((Object)(object)newRoot))
				{
					roots.Add(newRoot);
				}
			}
			if (newExcluded != null)
			{
				foreach (Transform item in newExcluded)
				{
					if (Object.op_Implicit((Object)(object)item))
					{
						excluded.Add(item);
					}
				}
			}
			nextRescan = 0f;
		}

		public void ClearRoots()
		{
			RestoreNow();
			roots.Clear();
		}

		public void SetVisible(bool visible, float hiddenAlpha)
		{
			target = (visible ? 1f : Mathf.Clamp01(hiddenAlpha));
		}

		public void RequestRescan()
		{
			nextRescan = 0f;
		}

		public void Tick(float fadeInSeconds, float fadeOutSeconds)
		{
			if (roots.Count == 0)
			{
				return;
			}
			float unscaledDeltaTime = Time.unscaledDeltaTime;
			if (target > current)
			{
				current = ((fadeInSeconds < 0.01f) ? target : Mathf.MoveTowards(current, target, unscaledDeltaTime / fadeInSeconds));
			}
			else if (target < current)
			{
				current = ((fadeOutSeconds < 0.01f) ? target : Mathf.MoveTowards(current, target, unscaledDeltaTime / fadeOutSeconds));
			}
			if (current < 0.999f)
			{
				if (Time.unscaledTime >= nextRescan)
				{
					Rescan();
					nextRescan = Time.unscaledTime + 0.35f;
				}
				Apply(current);
				touching = true;
			}
			else if (touching)
			{
				RestoreNow();
			}
		}

		public void SnapNow()
		{
			if (roots.Count != 0)
			{
				current = target;
				if (current < 0.999f)
				{
					Rescan();
					nextRescan = Time.unscaledTime + 0.35f;
					Apply(current);
					touching = true;
				}
				else if (touching)
				{
					RestoreNow();
				}
			}
		}

		public void RestoreNow()
		{
			for (int i = 0; i < entries.Count; i++)
			{
				Entry entry = entries[i];
				if (Object.op_Implicit((Object)(object)entry.Component))
				{
					SetAlpha(entry.Component, entry.Kind, entry.BaseAlpha);
				}
			}
			entries.Clear();
			current = 1f;
			touching = false;
			nextRescan = 0f;
		}

		private void Rescan()
		{
			seen.Clear();
			carryOver.Clear();
			for (int i = 0; i < entries.Count; i++)
			{
				if (Object.op_Implicit((Object)(object)entries[i].Component))
				{
					carryOver[entries[i].Component] = entries[i];
				}
			}
			entries.Clear();
			for (int j = 0; j < roots.Count; j++)
			{
				Transform val = roots[j];
				if (Object.op_Implicit((Object)(object)val))
				{
					Collect<SpriteRenderer>(val, Kind.Sprite);
					Collect<tk2dBaseSprite>(val, Kind.Tk2dSprite);
					Collect<tk2dTextMesh>(val, Kind.Tk2dText);
					Collect<Graphic>(val, Kind.Graphic);
					Collect<TMP_Text>(val, Kind.Tmp);
					Collect<TMP_Text>(val, Kind.TmpOld);
				}
			}
		}

		private void Collect<T>(Transform root, Kind kind) where T : Component
		{
			T[] componentsInChildren = ((Component)root).GetComponentsInChildren<T>(true);
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				Component val = (Component)(object)componentsInChildren[i];
				if (!IsExcluded(val.transform) && seen.Add(val))
				{
					if (carryOver.TryGetValue(val, out var value))
					{
						entries.Add(value);
						continue;
					}
					float alpha = GetAlpha(val, kind);
					entries.Add(new Entry
					{
						Component = val,
						Kind = kind,
						BaseAlpha = alpha,
						LastApplied = alpha
					});
				}
			}
		}

		private bool IsExcluded(Transform t)
		{
			for (int i = 0; i < excluded.Count; i++)
			{
				if (Object.op_Implicit((Object)(object)excluded[i]) && t.IsChildOf(excluded[i]))
				{
					return true;
				}
			}
			return false;
		}

		private void Apply(float multiplier)
		{
			for (int i = 0; i < entries.Count; i++)
			{
				Entry value = entries[i];
				if (Object.op_Implicit((Object)(object)value.Component))
				{
					float alpha = GetAlpha(value.Component, value.Kind);
					if (Mathf.Abs(alpha - value.LastApplied) > 0.004f)
					{
						value.BaseAlpha = alpha;
					}
					float num = value.BaseAlpha * multiplier;
					SetAlpha(value.Component, value.Kind, num);
					value.LastApplied = num;
					entries[i] = value;
				}
			}
		}

		private static float GetAlpha(Component c, Kind kind)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: 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_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_006a: Unknown result type (might be due to invalid IL or missing references)
			//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)
			return kind switch
			{
				Kind.Sprite => ((SpriteRenderer)c).color.a, 
				Kind.Tk2dSprite => ((tk2dBaseSprite)c).color.a, 
				Kind.Tk2dText => ((tk2dTextMesh)c).color.a, 
				Kind.Graphic => ((Graphic)c).color.a, 
				Kind.Tmp => ((Graphic)(TMP_Text)c).color.a, 
				Kind.TmpOld => ((Graphic)(TMP_Text)c).color.a, 
				_ => 1f, 
			};
		}

		private static void SetAlpha(Component c, Kind kind, float a)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0074: Unknown result type (might be due to invalid IL or missing references)
			//IL_0079: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0088: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: 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_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			switch (kind)
			{
			case Kind.Sprite:
			{
				SpriteRenderer val6 = (SpriteRenderer)c;
				Color color6 = val6.color;
				color6.a = a;
				val6.color = color6;
				break;
			}
			case Kind.Tk2dSprite:
			{
				tk2dBaseSprite val5 = (tk2dBaseSprite)c;
				Color color5 = val5.color;
				color5.a = a;
				val5.color = color5;
				break;
			}
			case Kind.Tk2dText:
			{
				tk2dTextMesh val4 = (tk2dTextMesh)c;
				Color color4 = val4.color;
				color4.a = a;
				val4.color = color4;
				break;
			}
			case Kind.Graphic:
			{
				Graphic val3 = (Graphic)c;
				Color color3 = val3.color;
				color3.a = a;
				val3.color = color3;
				break;
			}
			case Kind.Tmp:
			{
				TMP_Text val2 = (TMP_Text)c;
				Color color2 = ((Graphic)val2).color;
				color2.a = a;
				((Graphic)val2).color = color2;
				break;
			}
			case Kind.TmpOld:
			{
				TMP_Text val = (TMP_Text)c;
				Color color = ((Graphic)val).color;
				color.a = a;
				((Graphic)val).color = color;
				break;
			}
			}
		}
	}
	public sealed class HudElementRegistry
	{
		public readonly HudElement Health = new HudElement("Health");

		public readonly HudElement Silk = new HudElement("Silk");

		public readonly HudElement Tools = new HudElement("Tools");

		public readonly HudElement Others = new HudElement("Others");

		public readonly List<ToolHudIcon> ToolIcons = new List<ToolHudIcon>();

		private readonly ManualLogSource log;

		private readonly List<ToolHudIcon> subscribedIcons = new List<ToolHudIcon>();

		private readonly Action toolIconUpdatedHandler;

		private float nextAttempt;

		public Transform HudRoot { get; private set; }

		public bool Resolved { get; private set; }

		public HudElementRegistry(ManualLogSource log)
		{
			this.log = log;
			toolIconUpdatedHandler = Tools.RequestRescan;
		}

		public void Shutdown()
		{
			UnsubscribeToolIcons();
		}

		private void UnsubscribeToolIcons()
		{
			for (int i = 0; i < subscribedIcons.Count; i++)
			{
				if (subscribedIcons[i] != null)
				{
					subscribedIcons[i].Updated -= toolIconUpdatedHandler;
				}
			}
			subscribedIcons.Clear();
		}

		public IEnumerable<HudElement> AllElements()
		{
			yield return Health;
			yield return Silk;
			yield return Tools;
			yield return Others;
		}

		public void Invalidate()
		{
			nextAttempt = 0f;
		}

		public void Tick()
		{
			if ((!Resolved || !Health.RootsAlive() || !Silk.RootsAlive() || !ToolIconsAlive()) && !(Time.unscaledTime < nextAttempt))
			{
				nextAttempt = Time.unscaledTime + 2f;
				TryResolve();
			}
		}

		private bool ToolIconsAlive()
		{
			for (int i = 0; i < ToolIcons.Count; i++)
			{
				if (!Object.op_Implicit((Object)(object)ToolIcons[i]))
				{
					return false;
				}
			}
			return true;
		}

		private void TryResolve()
		{
			Resolved = false;
			HudCanvas val = Object.FindFirstObjectByType<HudCanvas>((FindObjectsInactive)1);
			GameCameras silentInstance = GameCameras.SilentInstance;
			SilkSpool spool = (Object.op_Implicit((Object)(object)silentInstance) ? silentInstance.silkSpool : null);
			if (!Object.op_Implicit((Object)(object)spool))
			{
				spool = Object.FindFirstObjectByType<SilkSpool>((FindObjectsInactive)1);
			}
			HudRoot = (Object.op_Implicit((Object)(object)val) ? ((Component)val).transform : (Object.op_Implicit((Object)(object)spool) ? ((Component)spool).transform.root : null));
			if ((Object)(object)HudRoot == (Object)null)
			{
				return;
			}
			UnsubscribeToolIcons();
			ToolIcons.Clear();
			ToolHudIcon[] array = Object.FindObjectsByType<ToolHudIcon>((FindObjectsInactive)1, (FindObjectsSortMode)0);
			foreach (ToolHudIcon val2 in array)
			{
				if (((Component)val2).transform.IsChildOf(HudRoot))
				{
					ToolIcons.Add(val2);
					val2.Updated += toolIconUpdatedHandler;
					subscribedIcons.Add(val2);
				}
			}
			List<Transform> healthAnchors = new List<Transform>();
			HealthSpecialHealIndicator[] array2 = Object.FindObjectsByType<HealthSpecialHealIndicator>((FindObjectsInactive)1, (FindObjectsSortMode)0);
			foreach (HealthSpecialHealIndicator val3 in array2)
			{
				if (((Component)val3).transform.IsChildOf(HudRoot))
				{
					healthAnchors.Add(((Component)val3).transform);
				}
			}
			Transform val4 = null;
			BindOrbHudFrame val5 = Object.FindFirstObjectByType<BindOrbHudFrame>((FindObjectsInactive)1);
			if (Object.op_Implicit((Object)(object)val5) && ((Component)val5).transform.IsChildOf(HudRoot))
			{
				val4 = ((Component)val5).transform;
				if (Object.op_Implicit((Object)(object)spool) && val4.IsChildOf(((Component)spool).transform) && (Object)(object)val4 != (Object)(object)((Component)spool).transform)
				{
					while ((Object)(object)val4.parent != (Object)null && (Object)(object)val4.parent != (Object)(object)((Component)spool).transform)
					{
						val4 = val4.parent;
					}
					int num = ((Component)val4).GetComponentsInChildren<Renderer>(true).Length;
					int num2 = ((Component)spool).GetComponentsInChildren<Renderer>(true).Length;
					if (num * 2 > num2)
					{
						log.LogWarning((object)$"Crest candidate {GetPath(val4)} holds {num}/{num2} spool renderers; using the bind orb object itself instead.");
						val4 = ((Component)val5).transform;
					}
				}
				else
				{
					val4 = ClimbWhileExclusive(val4, (Transform t) => SubtreeHasSilk(t) || SubtreeHasTool(t) || SubtreeHasHealth(t));
				}
			}
			if (!Object.op_Implicit((Object)(object)val4))
			{
				val4 = FindByNameFragment(HudRoot, new string[1] { "crest" }, (Transform t) => SubtreeHasTool(t) || SubtreeHasHealth(t));
			}
			if (Object.op_Implicit((Object)(object)spool))
			{
				Transform[] newExcluded = (Transform[])(object)((!Object.op_Implicit((Object)(object)val4) || !val4.IsChildOf(((Component)spool).transform)) ? null : new Transform[1] { val4 });
				Silk.SetRoots((IEnumerable<Transform>)(object)new Transform[1] { ((Component)spool).transform }, newExcluded);
			}
			HashSet<Transform> hashSet = new HashSet<Transform>();
			foreach (Transform item in healthAnchors)
			{
				hashSet.Add(ClimbWhileExclusive(item, (Transform t) => SubtreeHasSilk(t) || SubtreeHasTool(t)));
			}
			if (Object.op_Implicit((Object)(object)val4))
			{
				hashSet.Add(val4);
			}
			if (hashSet.Count == 0)
			{
				Transform val6 = FindByNameFragment(HudRoot, new string[4] { "health", "mask", "heart", "life" }, (Transform t) => SubtreeHasSilk(t) || SubtreeHasTool(t));
				if ((Object)(object)val6 != (Object)null)
				{
					hashSet.Add(val6);
					log.LogWarning((object)("Health element resolved by NAME fallback: " + GetPath(val6)));
				}
			}
			if (hashSet.Count > 0)
			{
				Health.SetRoots(hashSet);
			}
			HashSet<Transform> hashSet2 = new HashSet<Transform>();
			for (int num3 = 0; num3 < ToolIcons.Count; num3++)
			{
				if (Object.op_Implicit((Object)(object)ToolIcons[num3]))
				{
					hashSet2.Add(ClimbWhileExclusive(((Component)ToolIcons[num3]).transform, (Transform t) => SubtreeHasSilk(t) || SubtreeHasHealth(t)));
				}
			}
			if (hashSet2.Count > 0)
			{
				Tools.SetRoots(hashSet2);
			}
			List<Transform> list = new List<Transform>();
			HudGlobalHide[] array3 = Object.FindObjectsByType<HudGlobalHide>((FindObjectsInactive)1, (FindObjectsSortMode)0);
			for (int i = 0; i < array3.Length; i++)
			{
				Transform transform = ((Component)array3[i]).transform;
				if (transform.IsChildOf(HudRoot) && !Overlaps(transform, Silk.Roots) && !Overlaps(transform, Health.Roots) && !Overlaps(transform, Tools.Roots))
				{
					list.Add(transform);
				}
			}
			Others.SetRoots(list);
			Resolved = Silk.HasRoots && Health.HasRoots;
			StringBuilder stringBuilder = new StringBuilder("HUD element discovery:\n");
			stringBuilder.AppendLine("  HudRoot: " + GetPath(HudRoot));
			AppendGroup(stringBuilder, Silk);
			AppendGroup(stringBuilder, Health);
			AppendGroup(stringBuilder, Tools);
			AppendGroup(stringBuilder, Others);
			stringBuilder.AppendLine("  Crest icon: " + (Object.op_Implicit((Object)(object)val4) ? GetPath(val4) : "(not found — shares silk visibility)"));
			stringBuilder.Append($"  ToolHudIcons: {ToolIcons.Count}, resolved={Resolved}");
			if (Resolved)
			{
				log.LogInfo((object)stringBuilder.ToString());
			}
			else
			{
				log.LogWarning((object)(stringBuilder?.ToString() + "\n  Discovery incomplete — will retry. Press the dump key (default F9) in-game and share the log to fix resolvers."));
			}
			bool SubtreeHasHealth(Transform t)
			{
				for (int j = 0; j < healthAnchors.Count; j++)
				{
					if (Object.op_Implicit((Object)(object)healthAnchors[j]) && healthAnchors[j].IsChildOf(t))
					{
						return true;
					}
				}
				return false;
			}
			bool SubtreeHasSilk(Transform t)
			{
				if (Object.op_Implicit((Object)(object)spool))
				{
					return ((Component)spool).transform.IsChildOf(t);
				}
				return false;
			}
			bool SubtreeHasTool(Transform t)
			{
				for (int j = 0; j < ToolIcons.Count; j++)
				{
					if (Object.op_Implicit((Object)(object)ToolIcons[j]) && ((Component)ToolIcons[j]).transform.IsChildOf(t))
					{
						return true;
					}
				}
				return false;
			}
		}

		private Transform ClimbWhileExclusive(Transform start, Func<Transform, bool> parentForbidden)
		{
			Transform val = start;
			while ((Object)(object)val.parent != (Object)null && (Object)(object)val.parent != (Object)(object)HudRoot && !parentForbidden(val.parent))
			{
				val = val.parent;
			}
			return val;
		}

		private static Transform FindByNameFragment(Transform root, string[] fragments, Func<Transform, bool> forbidden)
		{
			Queue<Transform> queue = new Queue<Transform>();
			queue.Enqueue(root);
			while (queue.Count > 0)
			{
				Transform val = queue.Dequeue();
				if ((Object)(object)val != (Object)(object)root)
				{
					string text = ((Object)val).name.ToLowerInvariant();
					for (int i = 0; i < fragments.Length; i++)
					{
						if (text.Contains(fragments[i]) && !forbidden(val))
						{
							return val;
						}
					}
				}
				for (int j = 0; j < val.childCount; j++)
				{
					queue.Enqueue(val.GetChild(j));
				}
			}
			return null;
		}

		private static bool Overlaps(Transform t, IEnumerable<Transform> roots)
		{
			foreach (Transform root in roots)
			{
				if (Object.op_Implicit((Object)(object)root) && (t.IsChildOf(root) || root.IsChildOf(t)))
				{
					return true;
				}
			}
			return false;
		}

		private static void AppendGroup(StringBuilder sb, HudElement e)
		{
			sb.Append("  " + e.Name + ": ");
			if (!e.HasRoots)
			{
				sb.AppendLine("(none)");
				return;
			}
			bool flag = true;
			foreach (Transform root in e.Roots)
			{
				if (!flag)
				{
					sb.Append(" | ");
				}
				sb.Append(GetPath(root));
				flag = false;
			}
			sb.AppendLine();
		}

		public static string GetPath(Transform t)
		{
			if (!Object.op_Implicit((Object)(object)t))
			{
				return "(destroyed)";
			}
			StringBuilder stringBuilder = new StringBuilder(((Object)t).name);
			Transform parent = t.parent;
			while ((Object)(object)parent != (Object)null)
			{
				stringBuilder.Insert(0, ((Object)parent).name + "/");
				parent = parent.parent;
			}
			return stringBuilder.ToString();
		}
	}
	public static class HudHierarchyDumper
	{
		private const int MaxDepth = 12;

		private const int MaxLines = 1500;

		public static void Dump(Transform root, ManualLogSource log)
		{
			if (!Object.op_Implicit((Object)(object)root))
			{
				log.LogWarning((object)"HUD hierarchy dump: no root resolved yet (enter gameplay first).");
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.AppendLine("===== HUD hierarchy dump: " + HudElementRegistry.GetPath(root) + " =====");
			int lines = 0;
			Walk(root, 0, stringBuilder, ref lines);
			if (lines >= 1500)
			{
				stringBuilder.AppendLine("... (truncated)");
			}
			stringBuilder.Append("===== end dump =====");
			log.LogInfo((object)stringBuilder.ToString());
		}

		private static void Walk(Transform t, int depth, StringBuilder sb, ref int lines)
		{
			if (depth > 12 || lines >= 1500)
			{
				return;
			}
			lines++;
			sb.Append(new string(' ', depth * 2));
			sb.Append(((Object)t).name);
			sb.Append(((Component)t).gameObject.activeSelf ? "" : " [inactive]");
			Component[] components = ((Component)t).GetComponents<Component>();
			sb.Append("  <");
			for (int i = 0; i < components.Length; i++)
			{
				if (i > 0)
				{
					sb.Append(", ");
				}
				sb.Append(Object.op_Implicit((Object)(object)components[i]) ? ((object)components[i]).GetType().Name : "null");
			}
			sb.AppendLine(">");
			for (int j = 0; j < t.childCount; j++)
			{
				Walk(t.GetChild(j), depth + 1, sb, ref lines);
			}
		}
	}
	public sealed class PluginConfig
	{
		public sealed class ElementConfig
		{
			public readonly ConfigEntry<bool> Manage;

			public readonly ConfigEntry<float> AlwaysShowBelowPercent;

			public readonly ConfigEntry<float> ChangeLinger;

			internal ElementConfig(ConfigFile file, string section, float defaultThreshold, string what)
			{
				//IL_0058: Unknown result type (might be due to invalid IL or missing references)
				//IL_0062: Expected O, but got Unknown
				//IL_0099: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a3: Expected O, but got Unknown
				Manage = file.Bind<bool>(section, "Manage", true, "Auto-hide the " + what + ". When false the element is left fully visible at all times.");
				AlwaysShowBelowPercent = file.Bind<float>(section, "AlwaysShowBelowPercent", defaultThreshold, new ConfigDescription("While the " + what + " value is below this percentage of its current maximum, the element stays visible permanently.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 100f), Array.Empty<object>()));
				ChangeLinger = file.Bind<float>(section, "ChangeLingerSeconds", 5f, new ConfigDescription("When the " + what + " value changes (and is above the threshold), reveal the element and keep it visible this many seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			}
		}

		public readonly ConfigEntry<bool> Enabled;

		public readonly ConfigEntry<float> FadeOutSeconds;

		public readonly ConfigEntry<float> FadeInSeconds;

		public readonly ConfigEntry<float> HiddenAlpha;

		public readonly ConfigEntry<KeyboardShortcut> ForceShowKey;

		public readonly ConfigEntry<KeyboardShortcut> DumpHierarchyKey;

		public readonly ConfigEntry<float> CombatExitLinger;

		public readonly ConfigEntry<float> ProximityRadius;

		public readonly ConfigEntry<float> MaxEnemyZOffset;

		public readonly ConfigEntry<bool> RequireThreatCapability;

		public readonly ConfigEntry<bool> RequireLineOfSight;

		public readonly ConfigEntry<bool> RequireOnScreen;

		public readonly ConfigEntry<float> OnScreenPadding;

		public readonly ConfigEntry<bool> UseAggroDetection;

		public readonly ConfigEntry<float> AggroRadius;

		public readonly ConfigEntry<bool> HoldDuringArenas;

		public readonly ConfigEntry<float> ArenaMaxDistance;

		public readonly ConfigEntry<float> EnemyScanInterval;

		public readonly ConfigEntry<bool> LogCombatTriggers;

		public readonly ElementConfig Health;

		public readonly ElementConfig Silk;

		public readonly ElementConfig Tools;

		public readonly ConfigEntry<bool> ManageUnknownElements;

		public PluginConfig(ConfigFile file)
		{
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Expected O, but got Unknown
			//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			//IL_014b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0155: Expected O, but got Unknown
			//IL_0184: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Expected O, but got Unknown
			//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c7: Expected O, but got Unknown
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: Expected O, but got Unknown
			//IL_029f: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a9: Expected O, but got Unknown
			//IL_02f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02fe: Expected O, but got Unknown
			//IL_032d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0337: Expected O, but got Unknown
			Enabled = file.Bind<bool>("1. General", "Enabled", true, "Master switch. Disabling restores the vanilla always-on HUD.");
			FadeOutSeconds = file.Bind<float>("1. General", "FadeOutSeconds", 0.5f, new ConfigDescription("Duration of the fade-out animation when an element hides.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 3f), Array.Empty<object>()));
			FadeInSeconds = file.Bind<float>("1. General", "FadeInSeconds", 0.15f, new ConfigDescription("Duration of the fade-in animation when an element reveals. Keep short so danger is visible immediately.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 2f), Array.Empty<object>()));
			HiddenAlpha = file.Bind<float>("1. General", "HiddenAlpha", 0f, new ConfigDescription("Opacity of hidden elements. 0 = fully invisible, 0.15 = faint ghost.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			ForceShowKey = file.Bind<KeyboardShortcut>("1. General", "ForceShowKey", new KeyboardShortcut((KeyCode)291, Array.Empty<KeyCode>()), "Toggle a manual override that keeps the whole HUD visible.");
			DumpHierarchyKey = file.Bind<KeyboardShortcut>("1. General", "DumpHierarchyKey", new KeyboardShortcut((KeyCode)290, Array.Empty<KeyCode>()), "Dump the HUD GameObject hierarchy to the BepInEx log (troubleshooting element discovery).");
			CombatExitLinger = file.Bind<float>("2. Combat", "ExitLingerSeconds", 5f, new ConfigDescription("How long the HUD stays fully visible after the last combat signal (enemy nearby, aggro, damage taken).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 30f), Array.Empty<object>()));
			ProximityRadius = file.Bind<float>("2. Combat", "ProximityRadius", 25f, new ConfigDescription("World-space radius around Hornet within which any living enemy counts as combat (~25 is roughly one screen).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 100f), Array.Empty<object>()));
			MaxEnemyZOffset = file.Bind<float>("2. Combat", "MaxEnemyZOffset", 3f, new ConfigDescription("Enemies whose Z position is further than this from Hornet's plane are ignored (background/foreground decoration enemies that cannot interact with you).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.5f, 50f), Array.Empty<object>()));
			RequireThreatCapability = file.Bind<bool>("2. Combat", "RequireThreatCapability", true, "Ignore HealthManager entities that have neither a DamageHero component nor an AlertRange (ambient/decorative creatures like background fauna that cannot hurt or notice Hornet, e.g. mites, birds, breakable pottery). Disable if a real threat is being missed.");
			RequireLineOfSight = file.Bind<bool>("2. Combat", "RequireLineOfSight", true, "For the plain proximity check, ignore enemies with terrain blocking a direct line to Hornet (same technique the game's own AlertRange uses). Prevents enemies in an adjacent room or behind scenery from holding the HUD visible. Disable if this ever misses a real nearby threat.");
			RequireOnScreen = file.Bind<bool>("2. Combat", "RequireOnScreen", true, "For the plain proximity check, ignore enemies that are outside the main camera's viewport (e.g. above/below the visible room, out of sight). Aggro detection is unaffected and can still catch a threat that noticed Hornet from off-screen. Disable to fall back to pure world-space radius.");
			OnScreenPadding = file.Bind<float>("2. Combat", "OnScreenPadding", 0.15f, new ConfigDescription("Extra margin around the camera viewport (as a fraction of screen size) within which an enemy still counts as on-screen. Avoids flicker right at the edge.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			UseAggroDetection = file.Bind<bool>("2. Combat", "UseAggroDetection", true, "Also count enemies beyond the radius whose AlertRange reports Hornet detected (hybrid Elden-Ring-style trigger).");
			AggroRadius = file.Bind<float>("2. Combat", "AggroRadius", 60f, new ConfigDescription("Maximum distance at which an aggroed enemy still counts as combat. Prevents area-wide AlertRange triggers from keeping the HUD visible across an entire zone.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 200f), Array.Empty<object>()));
			HoldDuringArenas = file.Bind<bool>("2. Combat", "HoldDuringArenas", true, "Keep the HUD visible for the entire duration of arena / battle-gate encounters, ignoring the exit linger.");
			ArenaMaxDistance = file.Bind<float>("2. Combat", "ArenaMaxDistance", 100f, new ConfigDescription("An in-progress arena only holds the HUD while Hornet is within this distance of it (guards against stuck battle controllers in large scenes).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 300f), Array.Empty<object>()));
			EnemyScanInterval = file.Bind<float>("2. Combat", "EnemyScanIntervalSeconds", 0.25f, new ConfigDescription("How often the enemy list is scanned. Lower = more responsive, higher = cheaper.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.05f, 2f), Array.Empty<object>()));
			Health = new ElementConfig(file, "3. Health", 50f, "health masks");
			Silk = new ElementConfig(file, "4. Silk", 25f, "silk spool");
			Tools = new ElementConfig(file, "5. Tools", 0f, "tool ammo");
			ManageUnknownElements = file.Bind<bool>("6. Advanced", "ManageUnknownElements", false, "Also auto-hide HUD elements the mod could not classify (listed in the log at startup). They then follow combat visibility only.");
			LogCombatTriggers = file.Bind<bool>("6. Advanced", "LogCombatTriggers", false, "Log which enemy/arena starts and ends combat visibility (troubleshooting a HUD that stays visible).");
		}
	}
	public abstract class ResourceSensor
	{
		private readonly PluginConfig.ElementConfig cfg;

		private long lastStamp;

		private bool hasBaseline;

		private float lastChangeTime = float.NegativeInfinity;

		private float absorbUntil;

		public bool ShouldShow { get; private set; }

		protected ResourceSensor(PluginConfig.ElementConfig cfg)
		{
			this.cfg = cfg;
		}

		protected abstract bool TryRead(out long stamp, out float fraction);

		public void ForceReveal()
		{
			lastChangeTime = Time.time;
		}

		public void AbsorbChangesFor(float seconds)
		{
			absorbUntil = Time.unscaledTime + seconds;
		}

		public void Reset()
		{
			hasBaseline = false;
			lastChangeTime = float.NegativeInfinity;
		}

		public void Tick()
		{
			if (!TryRead(out var stamp, out var fraction))
			{
				ShouldShow = false;
				return;
			}
			float time = Time.time;
			if (hasBaseline && stamp != lastStamp && Time.unscaledTime >= absorbUntil)
			{
				lastChangeTime = time;
			}
			hasBaseline = true;
			lastStamp = stamp;
			bool flag = fraction * 100f < cfg.AlwaysShowBelowPercent.Value;
			bool flag2 = time - lastChangeTime <= cfg.ChangeLinger.Value;
			ShouldShow = flag || flag2;
		}
	}
	public sealed class HealthSensor : ResourceSensor
	{
		public HealthSensor(PluginConfig.ElementConfig cfg)
			: base(cfg)
		{
		}

		protected override bool TryRead(out long stamp, out float fraction)
		{
			stamp = 0L;
			fraction = 1f;
			PlayerData instance = PlayerData.instance;
			if (instance == null)
			{
				return false;
			}
			int currentMaxHealth = instance.CurrentMaxHealth;
			stamp = (long)instance.health * 1000000L + (long)instance.healthBlue * 1000L + currentMaxHealth;
			fraction = ((currentMaxHealth > 0) ? Mathf.Clamp01((float)instance.health / (float)currentMaxHealth) : 1f);
			return true;
		}
	}
	public sealed class SilkSensor : ResourceSensor
	{
		public SilkSensor(PluginConfig.ElementConfig cfg)
			: base(cfg)
		{
		}

		protected override bool TryRead(out long stamp, out float fraction)
		{
			stamp = 0L;
			fraction = 1f;
			PlayerData instance = PlayerData.instance;
			if (instance == null)
			{
				return false;
			}
			int currentSilkMax = instance.CurrentSilkMax;
			stamp = (long)instance.silk * 1000000L + currentSilkMax;
			fraction = ((currentSilkMax > 0) ? Mathf.Clamp01((float)instance.silk / (float)currentSilkMax) : 1f);
			return true;
		}
	}
	public sealed class ToolAmmoSensor : ResourceSensor
	{
		private readonly HudElementRegistry registry;

		private bool degraded;

		public ToolAmmoSensor(PluginConfig.ElementConfig cfg, HudElementRegistry registry)
			: base(cfg)
		{
			this.registry = registry;
		}

		protected override bool TryRead(out long stamp, out float fraction)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			stamp = 0L;
			fraction = 1f;
			if (degraded)
			{
				return false;
			}
			try
			{
				long num = 17L;
				int num2 = 0;
				int num3 = 0;
				bool flag = false;
				foreach (ToolHudIcon toolIcon in registry.ToolIcons)
				{
					if (!Object.op_Implicit((Object)(object)toolIcon))
					{
						continue;
					}
					ToolItem currentTool = toolIcon.CurrentTool;
					if (!((Object)(object)currentTool == (Object)null))
					{
						int toolStorageAmount = ToolItemManager.GetToolStorageAmount(currentTool);
						if (toolStorageAmount > 0)
						{
							int amountLeft = currentTool.SavedData.AmountLeft;
							num = num * 31 + amountLeft;
							num = num * 31 + toolStorageAmount;
							num2 += amountLeft;
							num3 += toolStorageAmount;
							flag = true;
						}
					}
				}
				if (!flag)
				{
					stamp = 0L;
					fraction = 1f;
					return true;
				}
				stamp = num;
				fraction = ((num3 > 0) ? Mathf.Clamp01((float)num2 / (float)num3) : 1f);
				return true;
			}
			catch (Exception ex)
			{
				degraded = true;
				HideHudPlugin.Log.LogWarning((object)("Tool ammo tracking disabled (game API mismatch?): " + ex.GetType().Name + ": " + ex.Message + ". Tools element now follows combat visibility only."));
				return false;
			}
		}
	}
}