Decompiled source of HotbarTint v1.0.5

HotbarTint.dll

Decompiled 12 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("HotbarTint")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("HotbarTint")]
[assembly: AssemblyTitle("HotbarTint")]
[assembly: AssemblyVersion("1.0.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 HotbarTint
{
	[BepInPlugin("lily.hotbartint", "Hotbar Tint", "1.0.5")]
	public class HotbarTintPlugin : BaseUnityPlugin
	{
		private class Slot
		{
			public int Column;

			public Transform Root;

			public RectTransform Bg;

			public Behaviour Fill;

			public GameObject Overlay;

			public Component Image;

			public bool Shown;
		}

		public const string Version = "1.0.5";

		private const float SearchInterval = 2f;

		private const float ConfigPollInterval = 1f;

		private const string OverlayName = "lilyTint";

		private const int MaxSlotDepth = 3;

		private ConfigEntry<string> _mode;

		private ConfigEntry<string> _color;

		private ConfigEntry<float> _opacity;

		private ConfigEntry<int> _glowWidth;

		private ConfigEntry<bool> _debugLog;

		private ConfigEntry<bool> _fixFill;

		private FieldInfo _localPlayer;

		private MethodInfo _getInventory;

		private MethodInfo _getItemAt;

		private FieldInfo _equipped;

		private FieldInfo _shared;

		private FieldInfo _itemName;

		private Type _imageType;

		private PropertyInfo _imgSprite;

		private PropertyInfo _imgColor;

		private PropertyInfo _imgRaycast;

		private bool _broken;

		private Transform _hotbar;

		private Transform _slotParent;

		private int _emptySearches;

		private int _errorsInARow;

		private readonly List<Slot> _slots = new List<Slot>();

		private float _nextSearch;

		private float _nextConfigPoll;

		private DateTime _configStamp;

		private bool _styleDirty = true;

		private Sprite _sprite;

		private TintMode _activeMode;

		private Color _activeColor;

		private string _lastEquippedLog;

		private void Awake()
		{
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected O, but got Unknown
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Expected O, but got Unknown
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e2: Expected O, but got Unknown
			_mode = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Mode", "Background", new ConfigDescription("How an equipped hotbar slot is marked.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "Background", "Glow", "Off" }), Array.Empty<object>()));
			_color = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Color", "#3C8CFF", "Marker color, #RRGGBB or #RRGGBBAA.");
			_opacity = ((BaseUnityPlugin)this).Config.Bind<float>("General", "Opacity", 0.6f, new ConfigDescription("Marker opacity.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			_glowWidth = ((BaseUnityPlugin)this).Config.Bind<int>("General", "GlowWidth", 3, new ConfigDescription("Rim width in px for Glow mode.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 6), Array.Empty<object>()));
			_fixFill = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "FixSeneaLFill", true, "SeneaL UI 1.2.2 leaves a slot's fill off after its item is unequipped, so the slot looks washed out. On = turn the fill back on for occupied, unequipped slots.");
			_debugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "DebugLog", false, "Log slot -> inventory cell -> item whenever the equipped set changes.");
			((BaseUnityPlugin)this).Config.SettingChanged += delegate
			{
				_styleDirty = true;
			};
			_configStamp = ConfigStamp();
			_broken = !ResolveReflection();
		}

		private bool ResolveReflection()
		{
			try
			{
				Type type = Type.GetType("Player, assembly_valheim", throwOnError: true);
				_localPlayer = type.GetField("m_localPlayer", BindingFlags.Static | BindingFlags.Public);
				_getInventory = type.GetMethod("GetInventory", BindingFlags.Instance | BindingFlags.Public, null, Type.EmptyTypes, null);
				_getItemAt = _getInventory.ReturnType.GetMethod("GetItemAt", new Type[2]
				{
					typeof(int),
					typeof(int)
				});
				Type returnType = _getItemAt.ReturnType;
				_equipped = returnType.GetField("m_equipped");
				_shared = returnType.GetField("m_shared");
				_itemName = _shared?.FieldType.GetField("m_name");
				_imageType = Type.GetType("UnityEngine.UI.Image, UnityEngine.UI", throwOnError: true);
				_imgSprite = _imageType.GetProperty("sprite");
				_imgColor = _imageType.GetProperty("color");
				_imgRaycast = _imageType.GetProperty("raycastTarget");
				if (_localPlayer == null || _equipped == null || _imgSprite == null || _imgColor == null || _imgRaycast == null)
				{
					throw new MissingMemberException("a required game/UI member is missing");
				}
				return true;
			}
			catch (Exception ex)
			{
				((BaseUnityPlugin)this).Logger.LogError((object)("HotbarTint disabled: " + ex.Message));
				return false;
			}
		}

		private void Update()
		{
			if (_broken)
			{
				return;
			}
			try
			{
				PollConfigFile();
				if (FindHotbar())
				{
					if (_styleDirty)
					{
						ApplyStyle();
					}
					MarkEquipped();
					_errorsInARow = 0;
				}
			}
			catch (Exception ex)
			{
				if (TintLogic.OnFrameError(++_errorsInARow) == ErrorAction.Disable)
				{
					_broken = true;
					((BaseUnityPlugin)this).Logger.LogError((object)$"HotbarTint disabled after {_errorsInARow} errors in a row: {ex}");
				}
				else
				{
					((BaseUnityPlugin)this).Logger.LogWarning((object)$"HotbarTint: error {_errorsInARow}/{3}, resetting the Hotbar cache: {ex}");
					ResetCache();
				}
			}
		}

		private void ResetCache()
		{
			_hotbar = null;
			_slotParent = null;
			_slots.Clear();
		}

		private void PollConfigFile()
		{
			if (Time.unscaledTime < _nextConfigPoll)
			{
				return;
			}
			_nextConfigPoll = Time.unscaledTime + 1f;
			DateTime dateTime = ConfigStamp();
			if (!(dateTime == _configStamp))
			{
				_configStamp = dateTime;
				bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet;
				((BaseUnityPlugin)this).Config.SaveOnConfigSet = false;
				try
				{
					((BaseUnityPlugin)this).Config.Reload();
				}
				finally
				{
					((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet;
				}
				_styleDirty = true;
			}
		}

		private DateTime ConfigStamp()
		{
			if (!File.Exists(((BaseUnityPlugin)this).Config.ConfigFilePath))
			{
				return DateTime.MinValue;
			}
			return File.GetLastWriteTimeUtc(((BaseUnityPlugin)this).Config.ConfigFilePath);
		}

		private bool FindHotbar()
		{
			if ((Object)(object)_hotbar != (Object)null && ((Component)_hotbar).gameObject.activeInHierarchy && _slots.Count > 0)
			{
				if (_slots.Exists((Slot s) => (Object)(object)s.Root == (Object)null || (Object)(object)s.Bg == (Object)null))
				{
					ScanSlots();
				}
				return _slots.Count > 0;
			}
			if (Time.unscaledTime < _nextSearch)
			{
				return false;
			}
			_nextSearch = Time.unscaledTime + 2f;
			ResetCache();
			bool flag = false;
			Transform[] array = Resources.FindObjectsOfTypeAll<Transform>();
			foreach (Transform val in array)
			{
				if (!(((Object)val).name != "Hotbar") && ((Component)val).gameObject.activeInHierarchy)
				{
					flag = true;
					_hotbar = val;
					ScanSlots();
					if (_slots.Count > 0)
					{
						break;
					}
				}
			}
			if (_slots.Count > 0)
			{
				_emptySearches = 0;
				if (_debugLog.Value)
				{
					ManualLogSource logger = ((BaseUnityPlugin)this).Logger;
					object arg = _slots.Count;
					Transform slotParent = _slotParent;
					logger.LogInfo((object)$"HotbarTint: found Hotbar with {arg} slots under {((slotParent != null) ? ((Object)slotParent).name : null)}");
				}
				return true;
			}
			_hotbar = null;
			if (flag && ++_emptySearches == 3)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"HotbarTint: found SeneaL's Hotbar but no slot1..slot8 with a bg under it; the HUD layout may have changed.");
			}
			return false;
		}

		private static Transform FindSlotParent(Transform root, int depth)
		{
			for (int i = 0; i < root.childCount; i++)
			{
				Transform child = root.GetChild(i);
				if (((Object)child).name == "slot1" && (Object)(object)child.Find("bg") != (Object)null)
				{
					return root;
				}
			}
			if (depth <= 1)
			{
				return null;
			}
			for (int j = 0; j < root.childCount; j++)
			{
				Transform val = FindSlotParent(root.GetChild(j), depth - 1);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			return null;
		}

		private static Behaviour FindFill(Transform slot)
		{
			Transform val = slot.Find("opq");
			if (!((Object)(object)val != (Object)null))
			{
				return null;
			}
			return ((Component)val).GetComponent<Behaviour>();
		}

		private void ScanSlots()
		{
			_slots.Clear();
			_slotParent = (((Object)(object)_hotbar != (Object)null) ? FindSlotParent(_hotbar, 3) : null);
			if ((Object)(object)_slotParent == (Object)null)
			{
				return;
			}
			for (int i = 0; i < _slotParent.childCount; i++)
			{
				Transform child = _slotParent.GetChild(i);
				int num = TintLogic.SlotColumn(((Object)child).name);
				if (num < 0)
				{
					continue;
				}
				Transform obj = child.Find("bg");
				RectTransform val = (RectTransform)(object)((obj is RectTransform) ? obj : null);
				if (!((Object)(object)val == (Object)null))
				{
					Transform val2 = child.Find("lilyTint");
					if ((Object)(object)val2 != (Object)null)
					{
						Object.Destroy((Object)(object)((Component)val2).gameObject);
					}
					_slots.Add(new Slot
					{
						Column = num,
						Root = child,
						Bg = val,
						Fill = FindFill(child)
					});
				}
			}
			_styleDirty = true;
		}

		private void ApplyStyle()
		{
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			_styleDirty = false;
			_activeMode = TintLogic.ParseMode(_mode.Value);
			Rgba rgba = TintLogic.ParseColor(_color.Value, _opacity.Value);
			_activeColor = new Color(rgba.R, rgba.G, rgba.B, rgba.A);
			if ((Object)(object)_sprite != (Object)null)
			{
				Object.Destroy((Object)(object)_sprite.texture);
				Object.Destroy((Object)(object)_sprite);
			}
			_sprite = BuildSprite(TintLogic.BuildMask(_activeMode, _glowWidth.Value));
			foreach (Slot slot in _slots)
			{
				if (!((Object)(object)slot.Overlay == (Object)null))
				{
					_imgSprite.SetValue(slot.Image, _sprite, null);
					_imgColor.SetValue(slot.Image, _activeColor, null);
				}
			}
		}

		private static Sprite BuildSprite(float[] mask)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			//IL_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			int num = 68;
			Texture2D val = new Texture2D(num, num, (TextureFormat)4, false)
			{
				name = "HotbarTintMask",
				filterMode = (FilterMode)1,
				wrapMode = (TextureWrapMode)1
			};
			Color[] array = (Color[])(object)new Color[num * num];
			for (int i = 0; i < num; i++)
			{
				for (int j = 0; j < num; j++)
				{
					array[(num - 1 - i) * num + j] = new Color(1f, 1f, 1f, mask[i * num + j]);
				}
			}
			val.SetPixels(array);
			val.Apply();
			return Sprite.Create(val, new Rect(0f, 0f, (float)num, (float)num), new Vector2(0.5f, 0.5f), 100f);
		}

		private void MarkEquipped()
		{
			object value = _localPlayer.GetValue(null);
			object obj = (((Object)((value is Object) ? value : null) != (Object)null) ? _getInventory.Invoke(value, null) : null);
			string text = (_debugLog.Value ? "" : null);
			foreach (Slot slot in _slots)
			{
				object obj2 = ((obj != null) ? _getItemAt.Invoke(obj, new object[2] { slot.Column, 0 }) : null);
				bool equipped = obj2 != null && (bool)_equipped.GetValue(obj2);
				bool flag = _activeMode != TintMode.Off && TintLogic.ShouldMark(obj2 != null, equipped);
				if (flag && text != null)
				{
					text += $" {((Object)slot.Root).name}=({slot.Column},0):{ItemName(obj2)}";
				}
				SetShown(slot, flag);
				if ((Object)(object)slot.Fill == (Object)null)
				{
					slot.Fill = FindFill(slot.Root);
				}
				if ((Object)(object)slot.Fill != (Object)null && TintLogic.ShouldRestoreFill(_fixFill.Value, obj2 != null, equipped, slot.Fill.enabled))
				{
					slot.Fill.enabled = true;
				}
			}
			if (text != null && text != _lastEquippedLog)
			{
				_lastEquippedLog = text;
				((BaseUnityPlugin)this).Logger.LogInfo((object)("HotbarTint equipped:" + ((text.Length == 0) ? " none" : text)));
			}
		}

		private string ItemName(object item)
		{
			object obj = _shared?.GetValue(item);
			if (obj == null)
			{
				return "?";
			}
			return (_itemName?.GetValue(obj) as string) ?? "?";
		}

		private void SetShown(Slot s, bool show)
		{
			if ((Object)(object)s.Overlay == (Object)null)
			{
				s.Shown = false;
				if (!show)
				{
					return;
				}
				CreateOverlay(s);
			}
			if (show != s.Shown)
			{
				s.Shown = show;
				s.Overlay.SetActive(show);
			}
		}

		private void CreateOverlay(Slot s)
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			//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_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: 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_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			//IL_006b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0094: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("lilyTint", new Type[1] { typeof(RectTransform) });
			RectTransform val2 = (RectTransform)val.transform;
			((Transform)val2).SetParent(s.Root, false);
			((Transform)val2).SetSiblingIndex(((Transform)s.Bg).GetSiblingIndex() + 1);
			val2.anchorMin = s.Bg.anchorMin;
			val2.anchorMax = s.Bg.anchorMax;
			val2.pivot = s.Bg.pivot;
			val2.anchoredPosition = s.Bg.anchoredPosition;
			val2.sizeDelta = s.Bg.sizeDelta;
			((Transform)val2).localScale = ((Transform)s.Bg).localScale;
			Component val3 = val.AddComponent(_imageType);
			_imgSprite.SetValue(val3, _sprite, null);
			_imgColor.SetValue(val3, _activeColor, null);
			_imgRaycast.SetValue(val3, false, null);
			val.SetActive(false);
			s.Overlay = val;
			s.Image = val3;
		}

		private void OnDestroy()
		{
			foreach (Slot slot in _slots)
			{
				if ((Object)(object)slot.Overlay != (Object)null)
				{
					Object.Destroy((Object)(object)slot.Overlay);
				}
			}
			if ((Object)(object)_sprite != (Object)null)
			{
				Object.Destroy((Object)(object)_sprite.texture);
				Object.Destroy((Object)(object)_sprite);
			}
		}
	}
	public enum TintMode
	{
		Background,
		Glow,
		Off
	}
	public enum ErrorAction
	{
		ResetCache,
		Disable
	}
	public struct Rgba
	{
		public float R;

		public float G;

		public float B;

		public float A;

		public Rgba(float r, float g, float b, float a)
		{
			R = r;
			G = g;
			B = b;
			A = a;
		}
	}
	public static class TintLogic
	{
		public const string DefaultColor = "#3C8CFF";

		public const float DefaultOpacity = 0.6f;

		public const int SlotCount = 8;

		public const int MaskSize = 68;

		public const float SlotInset = 6f;

		public const float CornerRadius = 8f;

		public const int MaxErrorsInARow = 3;

		public static int SlotColumn(string name)
		{
			if (name == null || !name.StartsWith("slot", StringComparison.Ordinal))
			{
				return -1;
			}
			if (!int.TryParse(name.Substring(4), NumberStyles.None, CultureInfo.InvariantCulture, out var result))
			{
				return -1;
			}
			if (name.Length != 4 + result.ToString(CultureInfo.InvariantCulture).Length)
			{
				return -1;
			}
			if (result < 1 || result > 8)
			{
				return -1;
			}
			return result - 1;
		}

		public static bool ShouldMark(bool hasItem, bool equipped)
		{
			return hasItem && equipped;
		}

		public static bool ShouldRestoreFill(bool fixEnabled, bool hasItem, bool equipped, bool fillEnabled)
		{
			if (fixEnabled && hasItem && !equipped)
			{
				return !fillEnabled;
			}
			return false;
		}

		public static ErrorAction OnFrameError(int errorsInARow)
		{
			if (errorsInARow < 3)
			{
				return ErrorAction.ResetCache;
			}
			return ErrorAction.Disable;
		}

		public static TintMode ParseMode(string text)
		{
			string text2 = (text ?? "").Trim().ToLowerInvariant();
			if (!(text2 == "glow"))
			{
				if (text2 == "off")
				{
					return TintMode.Off;
				}
				return TintMode.Background;
			}
			return TintMode.Glow;
		}

		public static Rgba ParseColor(string text, float opacity)
		{
			if (!TryParseHex(text, out var c))
			{
				TryParseHex("#3C8CFF", out c);
			}
			c.A *= Math.Max(0f, Math.Min(1f, opacity));
			return c;
		}

		private static bool TryParseHex(string text, out Rgba c)
		{
			c = default(Rgba);
			string text2 = (text ?? "").Trim();
			if (text2.StartsWith("#", StringComparison.Ordinal))
			{
				text2 = text2.Substring(1);
			}
			if (text2.Length != 6 && text2.Length != 8)
			{
				return false;
			}
			if (!uint.TryParse(text2, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out var result))
			{
				return false;
			}
			if (text2.Length == 6)
			{
				result = (result << 8) | 0xFF;
			}
			c = new Rgba((float)((result >> 24) & 0xFF) / 255f, (float)((result >> 16) & 0xFF) / 255f, (float)((result >> 8) & 0xFF) / 255f, (float)(result & 0xFF) / 255f);
			return true;
		}

		public static float RoundedRectCoverage(float px, float py, int size, float inset, float radius)
		{
			float num = (float)size / 2f - inset;
			float val = Math.Abs(px - (float)size / 2f) - (num - radius);
			float num2 = Math.Abs(py - (float)size / 2f) - (num - radius);
			float num3 = (float)Math.Sqrt(Math.Max(val, 0f) * Math.Max(val, 0f) + Math.Max(num2, 0f) * Math.Max(num2, 0f)) + Math.Min(Math.Max(val, num2), 0f) - radius;
			return Math.Max(0f, Math.Min(1f, 0.5f - num3));
		}

		public static float[] BuildMask(TintMode mode, int glowWidth)
		{
			int num = 68;
			int num2 = Math.Max(1, Math.Min(6, glowWidth));
			float[] array = new float[num * num];
			for (int i = 0; i < num; i++)
			{
				for (int j = 0; j < num; j++)
				{
					float num3 = RoundedRectCoverage((float)j + 0.5f, (float)i + 0.5f, num, 6f, 8f);
					float num4 = num3;
					switch (mode)
					{
					case TintMode.Glow:
					{
						float num5 = RoundedRectCoverage((float)j + 0.5f, (float)i + 0.5f, num, 6f - (float)num2, 8f + (float)num2);
						num4 = Math.Max(0f, num5 - num3);
						break;
					}
					case TintMode.Off:
						num4 = 0f;
						break;
					}
					array[i * num + j] = num4;
				}
			}
			return array;
		}
	}
}