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 WiresHealthAwareness v0.3.6
WiresHealthAwareness.dll
Decompiled 5 days agousing 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; } } }