Decompiled source of WiresHealthAwareness v0.3.6

WiresHealthAwareness.dll

Decompiled 5 days ago
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;

[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("WiresHealthAwareness")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("WiresHealthAwareness")]
[assembly: AssemblyTitle("WiresHealthAwareness")]
[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 WiresDamageVignette
{
	internal static class DamageGate
	{
		private static Player _player;

		private static float _previousHealth;

		private static float _previousMaximum;

		private static float _injuryPoints;

		private static bool _effectsArmed;

		private const float ScratchFloor = 0.01f;

		private const float ActivationFloor = 0.02f;

		private static bool _initialized;

		private static float _pendingDirectFlash;

		private const float DpsWindowSeconds = 1f;

		private const float DpsTrigger = 0.1f;

		private const float MinStreakHit = 0.015f;

		private const int HitSlots = 32;

		private static readonly float[] RecentTimes = new float[32];

		private static readonly float[] RecentFractions = new float[32];

		private static int _recentCount;

		private static int _recentNext;

		private static float _surgeBpmBoost;

		private static float _surgeHoldSeconds;

		private static float _lastStressUpdate;

		private static readonly Random StressRandom = new Random(Environment.TickCount ^ 0x6C4A32F);

		internal static bool EffectsArmed => _effectsArmed;

		internal static float StressBpmBoost => _surgeBpmBoost;

		internal static void Observe(Player player)
		{
			if ((Object)(object)player != (Object)(object)_player)
			{
				Reset();
				_player = player;
			}
			if ((Object)(object)player == (Object)null)
			{
				return;
			}
			float health = ((Character)player).GetHealth();
			float maxHealth = ((Character)player).GetMaxHealth();
			if (!_initialized)
			{
				_previousHealth = health;
				_previousMaximum = maxHealth;
				_initialized = true;
				_lastStressUpdate = Time.unscaledTime;
				return;
			}
			float num = health - _previousHealth;
			float num2 = maxHealth - _previousMaximum;
			if (num > 0.025f)
			{
				_injuryPoints = Mathf.Max(0f, _injuryPoints - num);
			}
			else if (num < -0.025f && num2 >= -0.025f && !((Character)player).IsDead())
			{
				float num3 = 0f - num;
				float num4 = num3 / Mathf.Max(1f, maxHealth);
				if (num4 >= 0.01f)
				{
					_injuryPoints += num3;
					if (num4 >= 0.02f || _injuryPoints >= maxHealth * 0.02f)
					{
						_effectsArmed = true;
					}
				}
			}
			if (maxHealth > 0f && health >= maxHealth - 0.025f)
			{
				_injuryPoints = 0f;
			}
			_injuryPoints = Mathf.Clamp(_injuryPoints, 0f, Mathf.Max(0f, maxHealth));
			if (_injuryPoints <= 0.025f)
			{
				_effectsArmed = false;
			}
			_previousHealth = health;
			_previousMaximum = maxHealth;
			AdvanceStress();
		}

		internal static void OnActualDamage(Player player, float healthBefore, HitData hit, bool triggerEffects)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return;
			}
			if ((Object)(object)player != (Object)(object)_player)
			{
				Reset();
				_player = player;
			}
			float health = ((Character)player).GetHealth();
			float num = healthBefore - health;
			if (num <= 0f)
			{
				return;
			}
			float num2 = Mathf.Max(1f, ((Character)player).GetMaxHealth());
			float num3 = Mathf.Clamp01(num / num2);
			_previousHealth = health;
			_previousMaximum = ((Character)player).GetMaxHealth();
			_initialized = true;
			if (_lastStressUpdate <= 0f)
			{
				_lastStressUpdate = Time.unscaledTime;
			}
			if (num3 < 0.01f)
			{
				return;
			}
			_injuryPoints = Mathf.Min(((Character)player).GetMaxHealth(), _injuryPoints + num);
			if (num3 >= 0.02f || _injuryPoints >= num2 * 0.02f)
			{
				_effectsArmed = true;
			}
			if (!IsDirectHit(hit, triggerEffects))
			{
				return;
			}
			_pendingDirectFlash = Mathf.Max(_pendingDirectFlash, Mathf.Lerp(0.7f, 1f, Mathf.Sqrt(Mathf.Clamp01(num3 * 3f))));
			AdvanceStress();
			if (num3 >= 0.4f)
			{
				float num4 = Mathf.Clamp01((num3 - 0.4f) / 0.6f);
				TriggerStress(Mathf.Lerp(26f, 46f, Mathf.Pow(num4, 0.85f)), Mathf.Lerp(0.55f, 0.85f, num4));
			}
			if (!(num3 >= 0.015f))
			{
				return;
			}
			int recentNext = _recentNext;
			RecentTimes[recentNext] = Time.unscaledTime;
			RecentFractions[recentNext] = num3;
			_recentNext = (_recentNext + 1) % 32;
			_recentCount = Mathf.Min(_recentCount + 1, 32);
			float num5 = 0f;
			int num6 = 0;
			for (int i = 0; i < _recentCount; i++)
			{
				if (Time.unscaledTime - RecentTimes[i] <= 1f)
				{
					num5 += RecentFractions[i] / 1f;
					num6++;
				}
			}
			if (num6 >= 2 && num5 >= 0.1f)
			{
				float num7 = Mathf.Clamp01((num5 - 0.1f) / 0.8f);
				TriggerStress(12f + 38f * Mathf.Pow(num7, 0.9f), Mathf.Lerp(0.42f, 0.7f, num7));
			}
		}

		internal static void OnStagger(Player player)
		{
			if (!((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer) && !((Character)player).IsDead())
			{
				if ((Object)(object)player != (Object)(object)_player)
				{
					Reset();
					_player = player;
				}
				AdvanceStress();
				TriggerStress(19f, 0.55f);
			}
		}

		private static void TriggerStress(float bpmBoost, float holdSeconds)
		{
			float num = 0.95f + (float)StressRandom.NextDouble() * 0.1f;
			_surgeBpmBoost = Mathf.Max(_surgeBpmBoost, bpmBoost * num);
			_surgeHoldSeconds = Mathf.Max(_surgeHoldSeconds, holdSeconds);
		}

		private static void AdvanceStress()
		{
			float unscaledTime = Time.unscaledTime;
			float num = Mathf.Clamp(unscaledTime - _lastStressUpdate, 0f, 0.5f);
			_lastStressUpdate = unscaledTime;
			if (!(num <= 0f))
			{
				float num2 = Mathf.Max(0f, num - _surgeHoldSeconds);
				_surgeHoldSeconds = Mathf.Max(0f, _surgeHoldSeconds - num);
				if (num2 > 0f)
				{
					_surgeBpmBoost = Mathf.MoveTowards(_surgeBpmBoost, 0f, 70f * num2);
				}
			}
		}

		internal static float ConsumeDirectFlash()
		{
			float pendingDirectFlash = _pendingDirectFlash;
			_pendingDirectFlash = 0f;
			return pendingDirectFlash;
		}

		private static bool IsDirectHit(HitData hit, bool triggerEffects)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: 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_0076: Expected I4, but got Unknown
			if (hit == null)
			{
				return false;
			}
			HitType hitType = hit.m_hitType;
			switch ((int)hitType)
			{
			case 4:
			case 5:
			case 6:
			case 7:
			case 8:
			case 9:
			case 10:
			case 20:
			case 21:
			case 22:
				return false;
			case 1:
			case 2:
			case 3:
			case 11:
			case 12:
			case 13:
			case 14:
			case 15:
			case 16:
			case 17:
			case 18:
			case 19:
			case 23:
			case 24:
				return true;
			case 0:
				return triggerEffects;
			default:
				return false;
			}
		}

		internal static void Reset()
		{
			_player = null;
			_previousHealth = 0f;
			_previousMaximum = 0f;
			_injuryPoints = 0f;
			_effectsArmed = false;
			_initialized = false;
			_pendingDirectFlash = 0f;
			_recentCount = 0;
			_recentNext = 0;
			_surgeBpmBoost = 0f;
			_surgeHoldSeconds = 0f;
			_lastStressUpdate = 0f;
		}
	}
	[HarmonyPatch(typeof(Character), "ApplyDamage")]
	internal static class ActualDamagePatch
	{
		[HarmonyPrefix]
		private static void Prefix(Character __instance, out float __state)
		{
			__state = (((Object)(object)__instance == (Object)(object)Player.m_localPlayer) ? __instance.GetHealth() : 0f);
		}

		[HarmonyPostfix]
		private static void Postfix(Character __instance, HitData hit, bool triggerEffects, float __state)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				DamageGate.OnActualDamage((Player)__instance, __state, hit, triggerEffects);
			}
		}
	}
	[HarmonyPatch(typeof(Character), "RPC_Stagger")]
	internal static class ActualStaggerPatch
	{
		[HarmonyPrefix]
		private static void Prefix(Character __instance, out bool __state)
		{
			__state = (Object)(object)__instance == (Object)(object)Player.m_localPlayer && !__instance.IsStaggering();
		}

		[HarmonyPostfix]
		private static void Postfix(Character __instance, bool __state)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			if (__state && (Object)(object)__instance == (Object)(object)Player.m_localPlayer)
			{
				DamageGate.OnStagger((Player)__instance);
			}
		}
	}
	internal sealed class HeartbeatSound : MonoBehaviour
	{
		private const string ResourceName = "WiresDamageVignette.heartbeat.wav";

		private const float OriginalBpm = 67f;

		private const float CriticalBpm = 110f;

		private const float BeatClipSeconds = 0.66f;

		private const float ClipFadeInSeconds = 0.006f;

		private const float ClipFadeOutSeconds = 0.04f;

		private static readonly float[] BeatOnsets = new float[10] { 0.18f, 1.08f, 1.96f, 2.86f, 3.8f, 4.65f, 5.53f, 6.41f, 7.3f, 8.24f };

		private AudioSource _source;

		private AudioClip _recording;

		private AudioClip[] _beatClips;

		private bool _ready;

		private bool _usingOneShots;

		private bool _wasActive;

		private float _secondsUntilBeat;

		private int _nextBeatIndex;

		private readonly Random _naturalRandom = new Random(Environment.TickCount ^ 0x56A91);

		private ManualLogSource _log;

		private void Awake()
		{
			_log = Logger.CreateLogSource("Wire's Health Awareness Audio");
			_source = ((Component)this).gameObject.AddComponent<AudioSource>();
			_source.playOnAwake = false;
			_source.loop = false;
			_source.pitch = 1f;
			_source.spatialBlend = 0f;
			_source.dopplerLevel = 0f;
			_source.priority = 96;
			_source.volume = 0f;
			_source.ignoreListenerPause = false;
			LoadEmbeddedAudio();
		}

		private void LoadEmbeddedAudio()
		{
			try
			{
				float[] array;
				int sampleRate;
				int channels;
				using (Stream stream = typeof(Plugin).Assembly.GetManifestResourceStream("WiresDamageVignette.heartbeat.wav"))
				{
					if (stream == null)
					{
						_log.LogError((object)"Embedded heartbeat audio resource missing: WiresDamageVignette.heartbeat.wav");
						return;
					}
					array = ReadPcm16Wave(stream, out sampleRate, out channels);
				}
				_recording = MakeClip("Wire_HealthAwareness_Heartbeat_Original", array, channels, sampleRate);
				if ((Object)(object)_recording == (Object)null)
				{
					_log.LogError((object)"Couldn't create heartbeat AudioClip from the embedded recording.");
					return;
				}
				try
				{
					_usingOneShots = CreateNativePitchBeats(array, sampleRate, channels);
				}
				catch (Exception ex)
				{
					_usingOneShots = false;
					_log.LogWarning((object)("Embedded heartbeat slicing failed; falling back to the original 67-BPM recording at natural pitch: " + ex.Message));
				}
				if (!_usingOneShots)
				{
					ReleaseBeatClips();
					_source.clip = _recording;
					_source.loop = true;
					_source.pitch = 1f;
					_log.LogWarning((object)"Using embedded original-speed 67-BPM heartbeat fallback.");
				}
				_ready = true;
				_log.LogInfo((object)("Embedded heartbeat ready: " + _recording.length.ToString("F2") + "s, " + _recording.channels + " channel(s); " + (_usingOneShots ? "native-pitch variable BPM, 10 recorded beats." : "original-speed fallback.")));
			}
			catch (Exception ex2)
			{
				_ready = false;
				ReleaseBeatClips();
				if ((Object)(object)_recording != (Object)null)
				{
					Object.Destroy((Object)(object)_recording);
					_recording = null;
				}
				_log.LogError((object)("Failed to load embedded heartbeat: " + ex2));
			}
		}

		private static AudioClip MakeClip(string name, float[] samples, int channels, int sampleRate)
		{
			if (samples == null || channels <= 0 || sampleRate <= 0 || samples.Length == 0 || samples.Length % channels != 0)
			{
				return null;
			}
			AudioClip val = AudioClip.Create(name, samples.Length / channels, channels, sampleRate, false);
			if ((Object)(object)val == (Object)null)
			{
				return null;
			}
			if (!SetSamples(val, samples))
			{
				Object.Destroy((Object)(object)val);
				return null;
			}
			return val;
		}

		private static bool SetSamples(AudioClip clip, float[] samples)
		{
			MethodInfo method = typeof(AudioClip).GetMethod("SetData", new Type[2]
			{
				typeof(float[]),
				typeof(int)
			});
			if (method != null)
			{
				return (bool)method.Invoke(clip, new object[2] { samples, 0 });
			}
			return false;
		}

		private bool CreateNativePitchBeats(float[] recordingSamples, int sampleRate, int channels)
		{
			int num = recordingSamples.Length / channels;
			if (sampleRate <= 0 || channels <= 0 || num <= 0)
			{
				return false;
			}
			_beatClips = (AudioClip[])(object)new AudioClip[BeatOnsets.Length];
			int num2 = Mathf.RoundToInt(0.66f * (float)sampleRate);
			int num3 = Mathf.Max(1, Mathf.RoundToInt(0.006f * (float)sampleRate));
			int num4 = Mathf.Max(1, Mathf.RoundToInt(0.04f * (float)sampleRate));
			for (int i = 0; i < BeatOnsets.Length; i++)
			{
				int num5 = Mathf.RoundToInt(BeatOnsets[i] * (float)sampleRate);
				if (num5 < 0 || num5 + num2 > num)
				{
					return false;
				}
				float[] array = new float[num2 * channels];
				Array.Copy(recordingSamples, num5 * channels, array, 0, array.Length);
				for (int j = 0; j < num2; j++)
				{
					float num6 = Mathf.Min(new float[3]
					{
						1f,
						(float)j / (float)num3,
						(float)(num2 - 1 - j) / (float)num4
					});
					for (int k = 0; k < channels; k++)
					{
						array[j * channels + k] *= num6;
					}
				}
				AudioClip val = MakeClip("Wire_RecordedBeat_" + (i + 1), array, channels, sampleRate);
				if ((Object)(object)val == (Object)null)
				{
					return false;
				}
				_beatClips[i] = val;
			}
			return true;
		}

		private static float[] ReadPcm16Wave(Stream stream, out int sampleRate, out int channels)
		{
			sampleRate = 0;
			channels = 0;
			ushort num = 0;
			ushort num2 = 0;
			byte[] array = null;
			using (BinaryReader binaryReader = new BinaryReader(stream))
			{
				if (new string(binaryReader.ReadChars(4)) != "RIFF")
				{
					throw new InvalidDataException("Expected RIFF WAV.");
				}
				binaryReader.ReadUInt32();
				if (new string(binaryReader.ReadChars(4)) != "WAVE")
				{
					throw new InvalidDataException("Expected WAVE.");
				}
				while (stream.Position + 8 <= stream.Length)
				{
					string text = new string(binaryReader.ReadChars(4));
					uint num3 = binaryReader.ReadUInt32();
					long num4 = stream.Position + num3;
					if (num4 > stream.Length)
					{
						throw new InvalidDataException("Corrupt WAV chunk length.");
					}
					if (text == "fmt ")
					{
						if (num3 < 16)
						{
							throw new InvalidDataException("Bad WAV fmt.");
						}
						num = binaryReader.ReadUInt16();
						channels = binaryReader.ReadUInt16();
						sampleRate = binaryReader.ReadInt32();
						binaryReader.ReadInt32();
						binaryReader.ReadUInt16();
						num2 = binaryReader.ReadUInt16();
					}
					else if (text == "data")
					{
						if (num3 > int.MaxValue)
						{
							throw new InvalidDataException("Oversized WAV.");
						}
						array = binaryReader.ReadBytes((int)num3);
					}
					stream.Position = num4 + (num3 & 1);
				}
			}
			if (num != 1 || num2 != 16 || array == null || channels <= 0 || sampleRate <= 0 || array.Length % (channels * 2) != 0)
			{
				throw new InvalidDataException("Heartbeat file must be PCM 16-bit WAV.");
			}
			short[] array2 = new short[array.Length / 2];
			Buffer.BlockCopy(array, 0, array2, 0, array.Length);
			float[] array3 = new float[array2.Length];
			for (int i = 0; i < array2.Length; i++)
			{
				array3[i] = (float)array2[i] / 32768f;
			}
			return array3;
		}

		internal void UpdateSound(bool active, float severity, float volume, VolumeCurve curve, float targetBpm, float variationPercent)
		{
			if ((Object)(object)_source == (Object)null || !_ready)
			{
				return;
			}
			float num = Mathf.Clamp01(severity);
			float num2 = 1.4912069f;
			float num3 = ((curve == VolumeCurve.Exponential) ? (0.58f * Mathf.Pow(1.6034484f, 2f * num)) : Mathf.Lerp(0.58f, num2, num));
			float num4 = (active ? Mathf.Clamp01(volume * 2.4f * num3) : 0f);
			if ((Object)(object)AudioMan.instance != (Object)null && (Object)(object)AudioMan.instance.m_guiMixer != (Object)null)
			{
				_source.outputAudioMixerGroup = AudioMan.instance.m_guiMixer;
			}
			float num5 = (active ? 3f : 2.7f);
			_source.volume = Mathf.MoveTowards(_source.volume, num4, Time.unscaledDeltaTime * num5);
			if (active)
			{
				if (!_wasActive)
				{
					_secondsUntilBeat = 0f;
					_wasActive = true;
				}
				if (_usingOneShots)
				{
					_secondsUntilBeat -= Time.unscaledDeltaTime;
					if (_secondsUntilBeat <= 0f)
					{
						_source.PlayOneShot(_beatClips[_nextBeatIndex], 1f);
						_nextBeatIndex = (_nextBeatIndex + 1) % _beatClips.Length;
						float num6 = Mathf.Clamp(variationPercent, 0f, 8f) * 0.01f;
						float num7 = Mathf.Sin(Time.unscaledTime * 1.208305f);
						float num8 = (float)(_naturalRandom.NextDouble() * 2.0 - 1.0);
						float num9 = num6 * (0.75f * num7 + 0.25f * num8);
						float num10 = Mathf.Clamp(targetBpm, 67f, 150f) * (1f + num9);
						float num11 = 60f / Mathf.Max(45f, num10);
						_secondsUntilBeat = num11 + Mathf.Max(-0.05f, _secondsUntilBeat);
					}
				}
				else if (!_source.isPlaying)
				{
					_source.pitch = 1f;
					_source.Play();
				}
			}
			else
			{
				_wasActive = false;
				_secondsUntilBeat = 0f;
				if (_source.isPlaying && _source.volume <= 0.0001f)
				{
					_source.Stop();
				}
			}
		}

		private void ReleaseBeatClips()
		{
			if (_beatClips == null)
			{
				return;
			}
			AudioClip[] beatClips = _beatClips;
			foreach (AudioClip val in beatClips)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
			}
			_beatClips = null;
		}

		internal void StopOnDeath()
		{
			if (!((Object)(object)_source == (Object)null))
			{
				_wasActive = false;
				_secondsUntilBeat = 0f;
				_source.volume = Mathf.MoveTowards(_source.volume, 0f, Mathf.Max(Time.unscaledDeltaTime, 0.004166667f) * 12.5f);
				if (_source.volume <= 0.001f)
				{
					_source.volume = 0f;
					_source.Stop();
				}
			}
		}

		internal void StopNow()
		{
			if (!((Object)(object)_source == (Object)null))
			{
				_source.volume = 0f;
				_source.Stop();
				_wasActive = false;
				_secondsUntilBeat = 0f;
			}
		}

		private void OnDestroy()
		{
			StopNow();
			ReleaseBeatClips();
			if ((Object)(object)_recording != (Object)null)
			{
				Object.Destroy((Object)(object)_recording);
			}
			if (_log != null)
			{
				Logger.Sources.Remove((ILogSource)(object)_log);
			}
		}
	}
	internal enum VolumeCurve
	{
		Exponential,
		Linear
	}
	[BepInPlugin("wire.valheim.healthawareness", "Wire's Health Awareness", "0.3.6")]
	[BepInProcess("valheim.exe")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "wire.valheim.healthawareness";

		public const string Name = "Wire's Health Awareness";

		public const string Version = "0.3.6";

		internal static ConfigEntry<bool> Enabled;

		internal static ConfigEntry<float> StartHealthPercent;

		internal static ConfigEntry<float> MaxOpacity;

		internal static ConfigEntry<bool> Pulse;

		internal static ConfigEntry<bool> HeartbeatEnabled;

		internal static ConfigEntry<float> HeartbeatVolume;

		internal static ConfigEntry<float> HeartbeatStartPercent;

		internal static ConfigEntry<VolumeCurve> HeartbeatCurve;

		internal static ConfigEntry<float> HeartbeatTempoVariation;

		internal static ConfigEntry<bool> VanillaDamageFlashEnabled;

		internal static ConfigEntry<bool> HitFlashEnabled;

		internal static ConfigEntry<float> HitFlashOpacity;

		internal static ConfigEntry<float> HitFlashDuration;

		private Harmony _harmony;

		private void Awake()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Expected O, but got Unknown
			//IL_0108: Unknown result type (might be due to invalid IL or missing references)
			//IL_0112: Expected O, but got Unknown
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			//IL_016f: Expected O, but got Unknown
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Expected O, but got Unknown
			//IL_021f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Expected O, but got Unknown
			//IL_025c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0266: 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_0324: Unknown result type (might be due to invalid IL or missing references)
			//IL_032e: Expected O, but got Unknown
			//IL_03fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_0404: Expected O, but got Unknown
			//IL_0475: Unknown result type (might be due to invalid IL or missing references)
			//IL_047f: Expected O, but got Unknown
			//IL_04f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0503: Expected O, but got Unknown
			//IL_0552: Unknown result type (might be due to invalid IL or missing references)
			//IL_055c: Expected O, but got Unknown
			//IL_05dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_05e6: Expected O, but got Unknown
			Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Vignette", "Enabled", true, "Display a red, texture-free vignette while below the configured health percentage.");
			StartHealthPercent = ((BaseUnityPlugin)this).Config.Bind<float>("Vignette", "Start below health (%)", 85f, new ConfigDescription("Health percentage where the vignette begins. Intensity increases smoothly toward zero health.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>()));
			MaxOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("Vignette", "Maximum opacity", 0.76f, new ConfigDescription("Maximum red-edge opacity at zero health. Does not darken the center of the screen.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			Pulse = ((BaseUnityPlugin)this).Config.Bind<bool>("Vignette", "Pulse", true, "Subtle pulsing as health drops; pulse intensity increases the lower your health gets.");
			HeartbeatEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Heartbeat", "Enabled", true, "Play the included recorded heartbeat loop below the separate heartbeat threshold.");
			HeartbeatStartPercent = ((BaseUnityPlugin)this).Config.Bind<float>("Heartbeat", "Start below health (%)", 65f, new ConfigDescription("Health percentage where the heartbeat begins; independent of the red vignette.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 100f), Array.Empty<object>()));
			HeartbeatCurve = ((BaseUnityPlugin)this).Config.Bind<VolumeCurve>("Heartbeat", "Loudness curve", VolumeCurve.Exponential, "Exponential builds faster as health falls. Linear increases evenly toward zero health.");
			HeartbeatVolume = ((BaseUnityPlugin)this).Config.Bind<float>("Heartbeat", "Volume", 0.5f, new ConfigDescription("Heartbeat loudness, routed through the game's SFX/GUI mixer and therefore controlled by both the in-game SFX and master volume settings.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 1f), Array.Empty<object>()));
			HeartbeatTempoVariation = ((BaseUnityPlugin)this).Config.Bind<float>("Heartbeat", "Natural BPM variation (%)", 3f, new ConfigDescription("Natural variation in the time BETWEEN beats, without changing pitch. Default 3% combines a gentle breathing-like rhythm with subtle beat-to-beat timing variation; 0% gives an exact steady BPM.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 8f), Array.Empty<object>()));
			VanillaDamageFlashEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Vanilla damage flash", "Enabled", false, "Show Valheim's original textured red damage screen on heavy hits. Disabled by default; enable to restore the vanilla visual. Wire's own smooth vignette and damage flash are unaffected.");
			HitFlashEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Damage flash", "Enabled", true, "Briefly flash a soft red vignette when a direct hit actually reduces health. Damage-over-time ticks never trigger the flash.");
			HitFlashOpacity = ((BaseUnityPlugin)this).Config.Bind<float>("Damage flash", "Flash opacity", 0.2f, new ConfigDescription("Strength of the direct-hit effect. At high health this controls the standalone flash opacity; at low health it controls how far the existing vignette expands inward without brightening. Harder hits have a stronger effect.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.1f, 0.5f), Array.Empty<object>()));
			HitFlashDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Damage flash", "Flash duration", 0.6f, new ConfigDescription("Total lifetime of the direct-hit flash in seconds. It appears almost immediately (~0.025 seconds) then fades out smoothly over the remaining time.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.15f, 1.5f), Array.Empty<object>()));
			ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("Internal", "Heartbeat volume updated to 0.1.1", false, new ConfigDescription("Internal upgrade marker.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Browsable = false
				}
			}));
			bool value = val.Value;
			if (!val.Value)
			{
				if (Mathf.Abs(HeartbeatVolume.Value - 0.28f) < 0.0001f)
				{
					HeartbeatVolume.Value = 0.38f;
				}
				val.Value = true;
				((BaseUnityPlugin)this).Config.Save();
			}
			ConfigEntry<bool> val2 = ((BaseUnityPlugin)this).Config.Bind<bool>("Internal", "Migrated to 0.2.0", false, new ConfigDescription("One-time default migration.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Browsable = false
				}
			}));
			bool value2 = val2.Value;
			if (!val2.Value)
			{
				if (Mathf.Abs(StartHealthPercent.Value - 50f) < 0.001f)
				{
					StartHealthPercent.Value = 65f;
				}
				if (value)
				{
					float value3 = HeartbeatVolume.Value;
					if (Mathf.Abs(value3 - 0.38f) < 0.001f)
					{
						HeartbeatVolume.Value = 0.5f;
					}
					else
					{
						HeartbeatVolume.Value = Mathf.Clamp01(value3 / 1.2f);
					}
				}
				val2.Value = true;
				((BaseUnityPlugin)this).Config.Save();
			}
			ConfigEntry<bool> val3 = ((BaseUnityPlugin)this).Config.Bind<bool>("Internal", "Migrated to 0.2.1", false, new ConfigDescription("Heartbeat volume scale migration.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Browsable = false
				}
			}));
			if (!val3.Value)
			{
				if (value2)
				{
					HeartbeatVolume.Value = Mathf.Clamp01(HeartbeatVolume.Value * 0.5f);
				}
				val3.Value = true;
				((BaseUnityPlugin)this).Config.Save();
			}
			ConfigEntry<bool> val4 = ((BaseUnityPlugin)this).Config.Bind<bool>("Internal", "Migrated flash duration to 0.2.4", false, new ConfigDescription("One-time hit flash duration default upgrade.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Browsable = false
				}
			}));
			if (!val4.Value)
			{
				if (Mathf.Abs(HitFlashDuration.Value - 0.34f) < 0.0001f)
				{
					HitFlashDuration.Value = 0.6f;
				}
				val4.Value = true;
				((BaseUnityPlugin)this).Config.Save();
			}
			ConfigEntry<bool> val5 = ((BaseUnityPlugin)this).Config.Bind<bool>("Internal", "Migrated flash strength to 0.2.8", false, new ConfigDescription("One-time direct-hit flash balance.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Browsable = false
				}
			}));
			if (!val5.Value)
			{
				val5.Value = true;
				((BaseUnityPlugin)this).Config.Save();
			}
			ConfigEntry<bool> val6 = ((BaseUnityPlugin)this).Config.Bind<bool>("Internal", "Migrated thresholds to 0.3.0", false, new ConfigDescription("One-time threshold default update.", (AcceptableValueBase)null, new object[1]
			{
				new ConfigurationManagerAttributes
				{
					Browsable = false
				}
			}));
			if (!val6.Value)
			{
				if (Mathf.Abs(StartHealthPercent.Value - 65f) < 0.0001f)
				{
					StartHealthPercent.Value = 85f;
				}
				if (Mathf.Abs(HeartbeatStartPercent.Value - 40f) < 0.0001f)
				{
					HeartbeatStartPercent.Value = 65f;
				}
				val6.Value = true;
				((BaseUnityPlugin)this).Config.Save();
			}
			RemoveLegacyColorSettings();
			_harmony = new Harmony("wire.valheim.healthawareness");
			_harmony.PatchAll();
			((Component)this).gameObject.AddComponent<HealthVignetteDriver>();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Wire's Health Awareness 0.3.6 loaded.");
		}

		private void OnDestroy()
		{
			DamageGate.Reset();
			Harmony harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
		}

		private void RemoveLegacyColorSettings()
		{
			string[] array = new string[8] { "Freezing", "Frost damage", "Burning", "Poisoned", "Smoke", "Spirit", "Lightning", "Tared" };
			foreach (string text in array)
			{
				ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("Status vignettes", text, true, "Removed in 0.2.2.");
				((BaseUnityPlugin)this).Config.Remove(((ConfigEntryBase)val).Definition);
			}
			((BaseUnityPlugin)this).Config.Save();
		}
	}
	internal sealed class ConfigurationManagerAttributes
	{
		public bool? Browsable = false;
	}
	internal sealed class HealthVignetteDriver : MonoBehaviour
	{
		private const int Rings = 30;

		private const int Segments = 96;

		private const float OuterRadius = 1.4142135f;

		private static readonly int SrcBlend = Shader.PropertyToID("_SrcBlend");

		private static readonly int DstBlend = Shader.PropertyToID("_DstBlend");

		private static readonly int Cull = Shader.PropertyToID("_Cull");

		private static readonly int ZWrite = Shader.PropertyToID("_ZWrite");

		private static readonly int ZTest = Shader.PropertyToID("_ZTest");

		private Material _overlay;

		private Camera _worldCamera;

		private float _strength;

		private float _flashElapsed = 999f;

		private float _flashStrength;

		private float _flashAttackStartAlpha;

		private float _pulsePhase;

		private HeartbeatSound _sound;

		private void OnEnable()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			Camera.onPostRender = (CameraCallback)Delegate.Combine((Delegate?)(object)Camera.onPostRender, (Delegate?)new CameraCallback(OnCameraPostRender));
			_sound = ((Component)this).gameObject.AddComponent<HeartbeatSound>();
		}

		private void OnDisable()
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: Expected O, but got Unknown
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			Camera.onPostRender = (CameraCallback)Delegate.Remove((Delegate?)(object)Camera.onPostRender, (Delegate?)new CameraCallback(OnCameraPostRender));
			if ((Object)(object)_sound != (Object)null)
			{
				_sound.StopNow();
			}
			DamageGate.Reset();
		}

		private void OnDestroy()
		{
			if ((Object)(object)_overlay != (Object)null)
			{
				Object.Destroy((Object)(object)_overlay);
			}
		}

		private void Update()
		{
			GameCamera instance = GameCamera.instance;
			_worldCamera = (((Object)(object)instance != (Object)null) ? ((Component)instance).GetComponent<Camera>() : null);
			Player localPlayer = Player.m_localPlayer;
			int num;
			object player;
			if ((Object)(object)localPlayer != (Object)null && !((Character)localPlayer).IsDead() && !((Character)localPlayer).IsTeleporting() && !GameCamera.InFreeFly() && (Object)(object)_worldCamera != (Object)null)
			{
				num = ((((Character)localPlayer).GetMaxHealth() > 0.001f) ? 1 : 0);
				if (num != 0)
				{
					player = localPlayer;
					goto IL_0069;
				}
			}
			else
			{
				num = 0;
			}
			player = null;
			goto IL_0069;
			IL_0069:
			DamageGate.Observe((Player)player);
			bool flag = num != 0 && DamageGate.EffectsArmed;
			float num2 = (flag ? (100f * Mathf.Clamp01(((Character)localPlayer).GetHealth() / ((Character)localPlayer).GetMaxHealth())) : 100f);
			float num3 = ((num != 0) ? DamageGate.StressBpmBoost : 0f);
			float num4 = Mathf.Max(1f, Plugin.StartHealthPercent.Value);
			float num5 = DamageGate.ConsumeDirectFlash();
			if (num == 0)
			{
				_flashElapsed = 999f;
				_flashStrength = 0f;
				_flashAttackStartAlpha = 0f;
			}
			else if (num5 > 0f)
			{
				_flashAttackStartAlpha = EvaluateFlashAlpha();
				_flashElapsed = 0f;
				_flashStrength = Mathf.Clamp01(num5);
			}
			else
			{
				_flashElapsed += Time.unscaledDeltaTime;
			}
			float num6 = ((flag && Plugin.Enabled.Value) ? Mathf.Clamp01((num4 - num2) / num4) : 0f);
			_strength = Mathf.MoveTowards(_strength, num6, Time.unscaledDeltaTime * 2.2f);
			float num7 = Mathf.Lerp(1.18f, 0.78f, _strength);
			_pulsePhase += Time.unscaledDeltaTime / num7;
			_pulsePhase -= Mathf.Floor(_pulsePhase);
			float num8 = Mathf.Max(1f, Plugin.HeartbeatStartPercent.Value);
			float num9 = (flag ? Mathf.Clamp01((num8 - num2) / num8) : 0f);
			bool active = num != 0 && Plugin.HeartbeatEnabled.Value && (num9 > 0.001f || num3 > 0.5f);
			float severity = Mathf.Max(num9, Mathf.Min(0.45f, num3 / 70f * 0.45f));
			float num10 = Mathf.Lerp(67f, 110f, Mathf.Pow(num9, 1.2f));
			float targetBpm = Mathf.Min(150f, num10 + num3);
			if ((Object)(object)_sound != (Object)null)
			{
				if ((Object)(object)localPlayer == (Object)null || ((Character)localPlayer).IsDead())
				{
					_sound.StopOnDeath();
				}
				else
				{
					_sound.UpdateSound(active, severity, Plugin.HeartbeatVolume.Value, Plugin.HeartbeatCurve.Value, targetBpm, Plugin.HeartbeatTempoVariation.Value);
				}
			}
		}

		private void OnCameraPostRender(Camera cam)
		{
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_worldCamera == (Object)null || (Object)(object)cam != (Object)(object)_worldCamera)
			{
				return;
			}
			float num = EvaluateFlashAlpha();
			if ((!(_strength <= 0.001f) || !(num <= 0.001f)) && EnsureMaterial())
			{
				float num2 = 0f;
				if (Plugin.Pulse.Value)
				{
					num2 = Peak(_pulsePhase, 0.12f, 0.068f) + 0.58f * Peak(_pulsePhase, 0.31f, 0.078f);
				}
				float num3 = Mathf.Clamp01(_strength);
				float num4 = Mathf.Clamp01(Plugin.MaxOpacity.Value);
				float num5 = Mathf.Pow(num3, 0.72f);
				float num6 = 0.12f * num2 * num5 * (1f - num5);
				float opacity = Mathf.Clamp01(num4 * (num5 + num6) + num * 1.75f);
				Color tint = default(Color);
				((Color)(ref tint))..ctor(1f, 0.3f, 0.36f);
				_overlay.SetInt(SrcBlend, 2);
				_overlay.SetInt(DstBlend, 10);
				RenderGradient(opacity, 1.25f, tint);
				_overlay.SetInt(SrcBlend, 1);
				_overlay.SetInt(DstBlend, 1);
				RenderGradient(opacity, 1.25f, new Color(0.14f, 0.004f, 0.008f));
			}
		}

		private void RenderGradient(float opacity, float falloffExponent, Color tint)
		{
			//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_009a: 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_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: 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_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: 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)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			_overlay.SetPass(0);
			GL.PushMatrix();
			GL.LoadOrtho();
			GL.Begin(4);
			for (int i = 0; i < 30; i++)
			{
				float num = 1.4142135f * (float)i / 30f;
				float num2 = 1.4142135f * ((float)i + 1f) / 30f;
				float alpha = RadialAlpha(num, falloffExponent) * opacity;
				float alpha2 = RadialAlpha(num2, falloffExponent) * opacity;
				for (int j = 0; j < 96; j++)
				{
					float angle = (float)Math.PI * 2f * (float)j / 96f;
					float angle2 = (float)Math.PI * 2f * ((float)j + 1f) / 96f;
					Vector2 p = Point(num, angle);
					Vector2 p2 = Point(num, angle2);
					Vector2 p3 = Point(num2, angle);
					Vector2 p4 = Point(num2, angle2);
					Vertex(p, alpha, tint);
					Vertex(p3, alpha2, tint);
					Vertex(p4, alpha2, tint);
					Vertex(p, alpha, tint);
					Vertex(p4, alpha2, tint);
					Vertex(p2, alpha, tint);
				}
			}
			GL.End();
			GL.PopMatrix();
		}

		private float EvaluateFlashAlpha()
		{
			if (!Plugin.HitFlashEnabled.Value)
			{
				return 0f;
			}
			float num = Mathf.Max(0.06f, Plugin.HitFlashDuration.Value);
			if (_flashElapsed >= num)
			{
				return 0f;
			}
			float num2 = Mathf.Min(0.025f, num * 0.12f);
			float num3 = Mathf.Clamp(Plugin.HitFlashOpacity.Value * _flashStrength, 0f, 0.5f);
			float num4 = Mathf.Max(_flashAttackStartAlpha, num3);
			if (_flashElapsed < num2)
			{
				float num5 = Mathf.Clamp01(_flashElapsed / num2);
				return Mathf.Lerp(_flashAttackStartAlpha, num4, Mathf.SmoothStep(0f, 1f, num5));
			}
			float num6 = Mathf.Clamp01((_flashElapsed - num2) / Mathf.Max(0.001f, num - num2));
			return num4 * (1f - Mathf.SmoothStep(0f, 1f, num6));
		}

		private bool EnsureMaterial()
		{
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			if ((Object)(object)_overlay != (Object)null)
			{
				return true;
			}
			Shader val = Shader.Find("Hidden/Internal-Colored");
			if ((Object)(object)val == (Object)null)
			{
				val = Shader.Find("Sprites/Default");
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			_overlay = new Material(val);
			((Object)_overlay).hideFlags = (HideFlags)61;
			_overlay.SetInt(SrcBlend, 2);
			_overlay.SetInt(DstBlend, 10);
			_overlay.SetInt(Cull, 0);
			_overlay.SetInt(ZWrite, 0);
			_overlay.SetInt(ZTest, 8);
			return true;
		}

		private static Vector2 Point(float r, float angle)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			return new Vector2(0.5f + 0.5f * r * Mathf.Cos(angle), 0.5f + 0.5f * r * Mathf.Sin(angle));
		}

		private static float RadialAlpha(float radius, float exponent)
		{
			return Mathf.Pow(Mathf.Clamp01(radius / 1.4142135f), exponent);
		}

		private static float Peak(float position, float center, float spread)
		{
			float num = Mathf.Abs(position - center);
			num = Mathf.Min(num, 1f - num);
			float num2 = num / spread;
			return Mathf.Exp((0f - num2) * num2 * 2f);
		}

		private static void Vertex(Vector2 p, float alpha, Color tint)
		{
			//IL_0000: 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_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: 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_0029: Unknown result type (might be due to invalid IL or missing references)
			GL.Color(new Color(tint.r * alpha, tint.g * alpha, tint.b * alpha, alpha));
			GL.Vertex3(p.x, p.y, 0f);
		}
	}
	[HarmonyPatch(typeof(Hud), "DamageFlash")]
	internal static class VanillaDamageFlashPatch
	{
		[HarmonyPrefix]
		private static bool Prefix()
		{
			if (Plugin.VanillaDamageFlashEnabled != null)
			{
				return Plugin.VanillaDamageFlashEnabled.Value;
			}
			return true;
		}
	}
}