Decompiled source of Better Low Health Warning v1.1.1

Better Low Health Warning.dll

Decompiled 3 days ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: AssemblyTitle("Better Low Health Warning")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Better Low Health Warning")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("4704650f-69e7-4932-a25e-3b23ceda6943")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace BetterLowHealthWarning;

[BepInPlugin("kumo.sulfur.better_low_health_warning", "Better Low Health Warning", "1.1.0")]
public sealed class Plugin : BaseUnityPlugin
{
	internal static ManualLogSource Log;

	internal static ConfigEntry<bool> EnableMod;

	internal static ConfigEntry<bool> HideWhenHudHidden;

	internal static ConfigEntry<float> WarningHealthPercent;

	internal static ConfigEntry<float> CriticalHealthPercent;

	internal static ConfigEntry<int> BorderThickness;

	internal static ConfigEntry<float> MinOpacity;

	internal static ConfigEntry<float> MaxOpacity;

	internal static ConfigEntry<bool> EnablePulse;

	internal static ConfigEntry<float> PulseSpeed;

	internal static ConfigEntry<float> FadeSpeed;

	internal static ConfigEntry<float> FullscreenTintOpacity;

	internal static ConfigEntry<bool> LogErrors;

	internal static BetterLowHealthWarningOverlay Overlay;

	private Harmony harmony;

	private static bool loggedError;

	private void Awake()
	{
		//IL_007a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Expected O, but got Unknown
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Expected O, but got Unknown
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fa: Expected O, but got Unknown
		//IL_012d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0137: Expected O, but got Unknown
		//IL_016a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0174: Expected O, but got Unknown
		//IL_01c7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d1: Expected O, but got Unknown
		//IL_0204: Unknown result type (might be due to invalid IL or missing references)
		//IL_020e: Expected O, but got Unknown
		//IL_0241: Unknown result type (might be due to invalid IL or missing references)
		//IL_024b: Expected O, but got Unknown
		//IL_0275: Unknown result type (might be due to invalid IL or missing references)
		//IL_027b: Expected O, but got Unknown
		//IL_029c: Unknown result type (might be due to invalid IL or missing references)
		//IL_02a6: Expected O, but got Unknown
		//IL_0317: Unknown result type (might be due to invalid IL or missing references)
		//IL_031d: Expected O, but got Unknown
		Log = ((BaseUnityPlugin)this).Logger;
		EnableMod = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableMod", true, "Enable better low health warning.");
		HideWhenHudHidden = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "HideWhenHudHidden", true, "Hide the warning when the game HUD is hidden.");
		WarningHealthPercent = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "WarningHealthPercent", 0.35f, new ConfigDescription("Show warning below this health percentage. 0.35 = 35%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		CriticalHealthPercent = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "CriticalHealthPercent", 0.15f, new ConfigDescription("Use stronger warning below this health percentage. 0.15 = 15%.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		BorderThickness = ((BaseUnityPlugin)this).Config.Bind<int>("Visual", "BorderThickness", 160, new ConfigDescription("Soft red vignette thickness in pixels.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 700), Array.Empty<object>()));
		MinOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "MinOpacity", 0.08f, new ConfigDescription("Warning opacity at the warning threshold.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		MaxOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "MaxOpacity", 0.42f, new ConfigDescription("Warning opacity at or below the critical threshold.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
		EnablePulse = ((BaseUnityPlugin)this).Config.Bind<bool>("Visual", "EnablePulse", true, "Pulse the warning when health is low.");
		PulseSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "PulseSpeed", 2.2f, new ConfigDescription("Pulse speed. Higher values pulse faster.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 20f), Array.Empty<object>()));
		FadeSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "FadeSpeed", 10f, new ConfigDescription("Fade in/out speed for the warning.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 50f), Array.Empty<object>()));
		FullscreenTintOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("Visual", "FullscreenTintOpacity", 0.035f, new ConfigDescription("Very subtle full-screen red tint multiplier. Set to 0 to disable.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 0.2f), Array.Empty<object>()));
		LogErrors = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "LogErrors", false, "Log patch errors. Keep false for normal gameplay.");
		GameObject val = new GameObject("BetterLowHealthWarningOverlay");
		Object.DontDestroyOnLoad((Object)(object)val);
		((Object)val).hideFlags = (HideFlags)61;
		Overlay = val.AddComponent<BetterLowHealthWarningOverlay>();
		harmony = new Harmony("kumo.sulfur.better_low_health_warning");
		Type type = AccessTools.TypeByName("PerfectRandom.Sulfur.Gameplay.PlayerHUD");
		if (type == null)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"Could not find PlayerHUD type.");
			return;
		}
		MethodInfo methodInfo = AccessTools.Method(type, "Update", (Type[])null, (Type[])null);
		if (methodInfo == null)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)"Could not find PlayerHUD.Update.");
			return;
		}
		HarmonyMethod val2 = new HarmonyMethod(typeof(Plugin).GetMethod("PlayerHudUpdatePostfix", BindingFlags.Static | BindingFlags.NonPublic));
		harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		((BaseUnityPlugin)this).Logger.LogInfo((object)"Better Low Health Warning 1.1.0 loaded. Patched PlayerHUD.Update.");
	}

	private void OnDestroy()
	{
		Harmony obj = harmony;
		if (obj != null)
		{
			obj.UnpatchSelf();
		}
		if ((Object)(object)Overlay != (Object)null)
		{
			Object.Destroy((Object)(object)((Component)Overlay).gameObject);
			Overlay = null;
		}
	}

	private static void PlayerHudUpdatePostfix(object __instance)
	{
		if ((Object)(object)Overlay == (Object)null)
		{
			return;
		}
		if (!EnableMod.Value)
		{
			Overlay.ClearHealth();
			return;
		}
		try
		{
			if (!TryGetNormalizedHealthFromPlayerHud(__instance, out var normalizedHealth))
			{
				Overlay.ClearHealth();
				return;
			}
			bool isHudVisible = TryGetHudVisible(__instance);
			Overlay.SetHealth(normalizedHealth, isHudVisible);
		}
		catch (Exception ex)
		{
			Overlay.ClearHealth();
			if (LogErrors.Value && !loggedError)
			{
				loggedError = true;
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)("Better Low Health Warning patch error: " + ex));
				}
			}
		}
	}

	private static bool TryGetNormalizedHealthFromPlayerHud(object playerHud, out float normalizedHealth)
	{
		normalizedHealth = 1f;
		if (playerHud == null)
		{
			return false;
		}
		object fieldValue = GetFieldValue(playerHud, "player");
		if (fieldValue == null)
		{
			return false;
		}
		object fieldValue2 = GetFieldValue(fieldValue, "playerUnit");
		if (fieldValue2 == null)
		{
			return false;
		}
		MethodInfo method = fieldValue2.GetType().GetMethod("GetNormalizedHealth", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (method == null)
		{
			return false;
		}
		object obj = method.Invoke(fieldValue2, null);
		if (!(obj is float))
		{
			return false;
		}
		normalizedHealth = Mathf.Clamp01((float)obj);
		return true;
	}

	private static bool TryGetHudVisible(object playerHud)
	{
		if (playerHud == null)
		{
			return true;
		}
		object fieldValue = GetFieldValue(playerHud, "canvasGroup");
		if (fieldValue == null)
		{
			return true;
		}
		PropertyInfo property = fieldValue.GetType().GetProperty("alpha", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (property == null)
		{
			return true;
		}
		object value = property.GetValue(fieldValue, null);
		if (!(value is float))
		{
			return true;
		}
		return (float)value > 0.01f;
	}

	private static object GetFieldValue(object instance, string fieldName)
	{
		if (instance == null || string.IsNullOrEmpty(fieldName))
		{
			return null;
		}
		FieldInfo field = instance.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		if (field == null)
		{
			return null;
		}
		return field.GetValue(instance);
	}
}
internal sealed class BetterLowHealthWarningOverlay : MonoBehaviour
{
	private float normalizedHealth = 1f;

	private bool hasValidHealth;

	private bool hudVisible = true;

	private float displayedOpacity;

	public void SetHealth(float value, bool isHudVisible)
	{
		normalizedHealth = Mathf.Clamp01(value);
		hudVisible = isHudVisible;
		hasValidHealth = true;
	}

	public void ClearHealth()
	{
		hasValidHealth = false;
		normalizedHealth = 1f;
		hudVisible = true;
	}

	private void OnGUI()
	{
		float num = 0f;
		if (Plugin.EnableMod.Value && hasValidHealth && (!Plugin.HideWhenHudHidden.Value || hudVisible))
		{
			float num2 = Mathf.Clamp01(Plugin.WarningHealthPercent.Value);
			float num3 = Mathf.Clamp01(Plugin.CriticalHealthPercent.Value);
			float num4 = Mathf.Max(num2, num3);
			float num5 = Mathf.Min(num2, num3);
			if (normalizedHealth <= num4)
			{
				float num6 = Mathf.Max(0.001f, num4 - num5);
				float num7 = Mathf.Clamp01((num4 - normalizedHealth) / num6);
				float num8 = Mathf.Clamp01(Plugin.MinOpacity.Value);
				float num9 = Mathf.Clamp01(Plugin.MaxOpacity.Value);
				if (num9 < num8)
				{
					float num10 = num8;
					num8 = num9;
					num9 = num10;
				}
				num = Mathf.Lerp(num8, num9, num7);
				if (Plugin.EnablePulse.Value)
				{
					float num11 = Mathf.Max(0.1f, Plugin.PulseSpeed.Value);
					float num12 = 0.5f + 0.5f * Mathf.Sin(Time.unscaledTime * num11 * (float)Math.PI * 2f);
					float num13 = Mathf.Lerp(0.05f, 0.35f, num7);
					num *= Mathf.Lerp(1f - num13, 1f, num12);
				}
			}
		}
		float num14 = Mathf.Max(0.1f, Plugin.FadeSpeed.Value);
		displayedOpacity = Mathf.Lerp(displayedOpacity, num, 1f - Mathf.Exp((0f - num14) * Time.unscaledDeltaTime));
		if (!(displayedOpacity <= 0.005f))
		{
			DrawSoftVignette(displayedOpacity);
		}
	}

	private static void DrawSoftVignette(float opacity)
	{
		//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_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: 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)
		//IL_011c: Unknown result type (might be due to invalid IL or missing references)
		//IL_013f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0168: Unknown result type (might be due to invalid IL or missing references)
		//IL_0197: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b8: Unknown result type (might be due to invalid IL or missing references)
		int num = Mathf.Clamp(Plugin.BorderThickness.Value, 1, 700);
		int num2 = 14;
		Texture2D whiteTexture = Texture2D.whiteTexture;
		Color color = GUI.color;
		float num3 = Screen.width;
		float num4 = Screen.height;
		float num5 = Mathf.Clamp(Plugin.FullscreenTintOpacity.Value, 0f, 0.2f);
		if (num5 > 0f)
		{
			GUI.color = new Color(0.45f, 0f, 0f, Mathf.Clamp01(opacity * num5));
			GUI.DrawTexture(new Rect(0f, 0f, num3, num4), (Texture)(object)whiteTexture);
		}
		float num6 = Mathf.Max(1f, (float)num / (float)num2);
		for (int i = 0; i < num2; i++)
		{
			float num7 = (float)i / (float)(num2 - 1);
			float num8 = opacity * Mathf.Pow(1f - num7, 1.85f);
			GUI.color = new Color(0.95f, 0.02f, 0.015f, Mathf.Clamp01(num8));
			float num9 = (float)i * num6;
			float num10 = Mathf.Ceil(num6);
			GUI.DrawTexture(new Rect(num9, num9, num3 - num9 * 2f, num10), (Texture)(object)whiteTexture);
			GUI.DrawTexture(new Rect(num9, num4 - num9 - num10, num3 - num9 * 2f, num10), (Texture)(object)whiteTexture);
			GUI.DrawTexture(new Rect(num9, num9 + num10, num10, num4 - num9 * 2f - num10 * 2f), (Texture)(object)whiteTexture);
			GUI.DrawTexture(new Rect(num3 - num9 - num10, num9 + num10, num10, num4 - num9 * 2f - num10 * 2f), (Texture)(object)whiteTexture);
		}
		GUI.color = color;
	}
}