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 damageSubscriptions = new HashSet(); private readonly HashSet confirmedCombatants = new HashSet(); 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((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(); if (!value3 || confirmedCombatants.Contains(item) || !((Object)(object)componentInChildren == (Object)null) || !((Object)(object)((Component)item).GetComponentInChildren() == (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 roots = new List(); private readonly List excluded = new List(); private readonly List entries = new List(); private readonly Dictionary carryOver = new Dictionary(); private readonly HashSet seen = new HashSet(); private float current = 1f; private float target = 1f; private float nextRescan; private bool touching; public bool HasRoots => roots.Count > 0; public IEnumerable 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 newRoots, IEnumerable 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(val, Kind.Sprite); Collect(val, Kind.Tk2dSprite); Collect(val, Kind.Tk2dText); Collect(val, Kind.Graphic); Collect(val, Kind.Tmp); Collect(val, Kind.TmpOld); } } } private void Collect(Transform root, Kind kind) where T : Component { T[] componentsInChildren = ((Component)root).GetComponentsInChildren(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 ToolIcons = new List(); private readonly ManualLogSource log; private readonly List subscribedIcons = new List(); 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 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((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((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((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 healthAnchors = new List(); HealthSpecialHealIndicator[] array2 = Object.FindObjectsByType((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((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(true).Length; int num2 = ((Component)spool).GetComponentsInChildren(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)(object)new Transform[1] { ((Component)spool).transform }, newExcluded); } HashSet hashSet = new HashSet(); 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 hashSet2 = new HashSet(); 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 list = new List(); HudGlobalHide[] array3 = Object.FindObjectsByType((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 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 forbidden) { Queue queue = new Queue(); 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 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(); 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 Manage; public readonly ConfigEntry AlwaysShowBelowPercent; public readonly ConfigEntry 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(section, "Manage", true, "Auto-hide the " + what + ". When false the element is left fully visible at all times."); AlwaysShowBelowPercent = file.Bind(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(0f, 100f), Array.Empty())); ChangeLinger = file.Bind(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(0f, 30f), Array.Empty())); } } public readonly ConfigEntry Enabled; public readonly ConfigEntry FadeOutSeconds; public readonly ConfigEntry FadeInSeconds; public readonly ConfigEntry HiddenAlpha; public readonly ConfigEntry ForceShowKey; public readonly ConfigEntry DumpHierarchyKey; public readonly ConfigEntry CombatExitLinger; public readonly ConfigEntry ProximityRadius; public readonly ConfigEntry MaxEnemyZOffset; public readonly ConfigEntry RequireThreatCapability; public readonly ConfigEntry RequireLineOfSight; public readonly ConfigEntry RequireOnScreen; public readonly ConfigEntry OnScreenPadding; public readonly ConfigEntry UseAggroDetection; public readonly ConfigEntry AggroRadius; public readonly ConfigEntry HoldDuringArenas; public readonly ConfigEntry ArenaMaxDistance; public readonly ConfigEntry EnemyScanInterval; public readonly ConfigEntry LogCombatTriggers; public readonly ElementConfig Health; public readonly ElementConfig Silk; public readonly ElementConfig Tools; public readonly ConfigEntry 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("1. General", "Enabled", true, "Master switch. Disabling restores the vanilla always-on HUD."); FadeOutSeconds = file.Bind("1. General", "FadeOutSeconds", 0.5f, new ConfigDescription("Duration of the fade-out animation when an element hides.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 3f), Array.Empty())); FadeInSeconds = file.Bind("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(0f, 2f), Array.Empty())); HiddenAlpha = file.Bind("1. General", "HiddenAlpha", 0f, new ConfigDescription("Opacity of hidden elements. 0 = fully invisible, 0.15 = faint ghost.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 1f), Array.Empty())); ForceShowKey = file.Bind("1. General", "ForceShowKey", new KeyboardShortcut((KeyCode)291, Array.Empty()), "Toggle a manual override that keeps the whole HUD visible."); DumpHierarchyKey = file.Bind("1. General", "DumpHierarchyKey", new KeyboardShortcut((KeyCode)290, Array.Empty()), "Dump the HUD GameObject hierarchy to the BepInEx log (troubleshooting element discovery)."); CombatExitLinger = file.Bind("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(0f, 30f), Array.Empty())); ProximityRadius = file.Bind("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(5f, 100f), Array.Empty())); MaxEnemyZOffset = file.Bind("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(0.5f, 50f), Array.Empty())); RequireThreatCapability = file.Bind("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("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("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("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(0f, 1f), Array.Empty())); UseAggroDetection = file.Bind("2. Combat", "UseAggroDetection", true, "Also count enemies beyond the radius whose AlertRange reports Hornet detected (hybrid Elden-Ring-style trigger)."); AggroRadius = file.Bind("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(10f, 200f), Array.Empty())); HoldDuringArenas = file.Bind("2. Combat", "HoldDuringArenas", true, "Keep the HUD visible for the entire duration of arena / battle-gate encounters, ignoring the exit linger."); ArenaMaxDistance = file.Bind("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(20f, 300f), Array.Empty())); EnemyScanInterval = file.Bind("2. Combat", "EnemyScanIntervalSeconds", 0.25f, new ConfigDescription("How often the enemy list is scanned. Lower = more responsive, higher = cheaper.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 2f), Array.Empty())); 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("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("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; } } } }