Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of IntelligentAutoHideHUD v0.1.0
HideHudSilksong.dll
Decompiled a day agousing 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; } } } }