Decompiled source of FuryoftheFallen v1.0.1

Mods/Fury of the Fallen.dll

Decompiled 8 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using FuryOfTheFallen;
using HarmonyLib;
using Il2CppRUMBLE.Managers;
using Il2CppRUMBLE.Players.Subsystems;
using Il2CppRUMBLE.Utilities;
using MelonLoader;
using Microsoft.CodeAnalysis;
using RumbleModUI;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(FuryOfTheFallenMod), "Fury of the Fallen", "1.0.1", "Nano", null)]
[assembly: MelonColor(127, 52, 235, 131)]
[assembly: MelonGame("Buckethead Entertainment", "RUMBLE")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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 FuryOfTheFallen
{
	public sealed class FuryOfTheFallenMod : MelonMod
	{
		[CompilerGenerated]
		private sealed class <HeartbeatLoop>d__74 : IEnumerator<object>, IEnumerator, IDisposable
		{
			private int <>1__state;

			private object <>2__current;

			public FuryOfTheFallenMod <>4__this;

			object IEnumerator<object>.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			object IEnumerator.Current
			{
				[DebuggerHidden]
				get
				{
					return <>2__current;
				}
			}

			[DebuggerHidden]
			public <HeartbeatLoop>d__74(int <>1__state)
			{
				this.<>1__state = <>1__state;
			}

			[DebuggerHidden]
			void IDisposable.Dispose()
			{
				<>1__state = -2;
			}

			private bool MoveNext()
			{
				//IL_0096: Unknown result type (might be due to invalid IL or missing references)
				//IL_00a0: Expected O, but got Unknown
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				//IL_0034: Unknown result type (might be due to invalid IL or missing references)
				//IL_0046: Unknown result type (might be due to invalid IL or missing references)
				//IL_0054: Unknown result type (might be due to invalid IL or missing references)
				int num = <>1__state;
				FuryOfTheFallenMod furyOfTheFallenMod = <>4__this;
				switch (num)
				{
				default:
					return false;
				case 0:
					<>1__state = -1;
					break;
				case 1:
					<>1__state = -1;
					break;
				}
				if (furyOfTheFallenMod._furyVisualShouldBeActive && furyOfTheFallenMod._heartbeatCall != null && furyOfTheFallenMod._audioPlaySoundMethod != null)
				{
					Vector3 position = (((Object)(object)furyOfTheFallenMod._playerHead != (Object)null) ? furyOfTheFallenMod._playerHead.position : Vector3.zero);
					try
					{
						object[] parameters = furyOfTheFallenMod.BuildPlaySoundArgs(furyOfTheFallenMod._audioPlaySoundMethod, furyOfTheFallenMod._heartbeatCall, position);
						furyOfTheFallenMod._heartbeatSource = furyOfTheFallenMod._audioPlaySoundMethod.Invoke(null, parameters);
					}
					catch (Exception ex)
					{
						((MelonBase)furyOfTheFallenMod).LoggerInstance.Warning("Heartbeat play failed: " + ex.Message);
					}
					<>2__current = (object)new WaitForSeconds(0.85f);
					<>1__state = 1;
					return true;
				}
				furyOfTheFallenMod._heartbeatLoopRunning = false;
				furyOfTheFallenMod._heartbeatSource = null;
				return false;
			}

			bool IEnumerator.MoveNext()
			{
				//ILSpy generated this explicit interface implementation from .override directive in MoveNext
				return this.MoveNext();
			}

			[DebuggerHidden]
			void IEnumerator.Reset()
			{
				throw new NotSupportedException();
			}
		}

		private const int EnabledSettingIndex = 0;

		private const int AlwaysOnSettingIndex = 1;

		private const int ActivationHealthSettingIndex = 2;

		private const int OverlayStrengthSettingIndex = 3;

		private const int PanicScaleAmountSettingIndex = 4;

		private const float OverlayFadeSpeed = 3.5f;

		private const float HmdMaskScaleMultiplier = 0.2f;

		private const float DefaultPanicScaleStrength = 0.15f;

		private const float PanicScaleSpeed = 8.5f;

		private const float HeartbeatVolume = 0.8f;

		private readonly Mod _modUi = new Mod();

		private readonly List<ModSetting> _settings = new List<ModSetting>();

		private bool _effectEnabled = true;

		private bool _alwaysOn;

		private int _activationHealth = 1;

		private float _overlayStrength = 0.85f;

		private float _panicScaleStrength = 0.15f;

		private short _lastKnownHealth = short.MaxValue;

		private bool _furyVisualShouldBeActive;

		private float _currentOverlayAlpha;

		private bool? _lastLoggedActivationState;

		private bool? _lastLoggedVisualVisibility;

		private float _nextModUiRegisterTryTime;

		private bool _modUiRegistered;

		private bool _modUiBuilt;

		private float _nextReferenceCheckTime;

		private Transform _playerHead;

		private bool _speedLinesReady;

		private float _animatedPhase;

		private GameObject _furySpeedLinesRoot;

		private GameObject _furySpeedLinesOverlay;

		private GameObject _furyHmdSpeedLinesOverlay;

		private Material _speedLinesMaterial;

		private Material _hmdSpeedLinesMaterial;

		private Vector3 _furyFlatBaseScale = Vector3.one;

		private Vector3 _furyHmdBaseScale = Vector3.one;

		private object _heartbeatCall;

		private object _heartbeatSource;

		private bool _heartbeatLoopRunning;

		private string _heartbeatPath;

		private string _heartbeatRelativePath;

		private MethodInfo _audioCreateCallMethod;

		private MethodInfo _audioPlaySoundMethod;

		private float _nextHeartbeatInitTryTime;

		internal static FuryOfTheFallenMod Instance { get; private set; }

		public override void OnInitializeMelon()
		{
			Instance = this;
			((MelonBase)this).HarmonyInstance.PatchAll(typeof(LocalHealthPatch));
			((MelonBase)this).LoggerInstance.Msg("Harmony patch applied: PlayerHealth.SetHealth");
		}

		public override void OnLateInitializeMelon()
		{
			TrySubscribeUiInitialized();
			LoadHeartbeatAudio();
		}

		public override void OnSceneWasLoaded(int buildIndex, string sceneName)
		{
			_nextReferenceCheckTime = 0f;
			_speedLinesReady = false;
			_playerHead = null;
			_lastLoggedVisualVisibility = null;
			_nextHeartbeatInitTryTime = 0f;
		}

		public override void OnSceneWasUnloaded(int buildIndex, string sceneName)
		{
			_furyVisualShouldBeActive = false;
			_lastKnownHealth = short.MaxValue;
			_lastLoggedActivationState = null;
			_lastLoggedVisualVisibility = null;
			StopHeartbeat();
			ApplySpeedLinesVisuals(0f);
		}

		public override void OnUpdate()
		{
			if (!_modUiRegistered && _modUiBuilt && Time.unscaledTime >= _nextModUiRegisterTryTime)
			{
				TryRegisterModUi();
				_nextModUiRegisterTryTime = Time.unscaledTime + 1f;
			}
			if (!_speedLinesReady)
			{
				EnsureSpeedLinesClone();
			}
			if (Time.unscaledTime >= _nextReferenceCheckTime)
			{
				RefreshPlayerHeadReference();
				_nextReferenceCheckTime = Time.unscaledTime + 1f;
			}
			float num = (_furyVisualShouldBeActive ? _overlayStrength : 0f);
			_currentOverlayAlpha = Mathf.MoveTowards(_currentOverlayAlpha, num, 3.5f * Time.unscaledDeltaTime);
			float num2 = (_furyVisualShouldBeActive ? (0.88f + 0.12f * Mathf.Sin(Time.unscaledTime * 9f)) : 1f);
			float alpha = _currentOverlayAlpha * num2;
			if (_furyVisualShouldBeActive)
			{
				_animatedPhase += Time.unscaledDeltaTime;
			}
			else
			{
				StopHeartbeat();
			}
			if (_furyVisualShouldBeActive)
			{
				EnsureHeartbeatAudioCall();
				EnsureHeartbeatLoop();
			}
			UpdateHmdOverlayPose();
			ApplySpeedLinesVisuals(alpha);
		}

		internal void HandleHealthChanged(PlayerHealth health, short newHealth, short previousHealth, bool useEffects)
		{
			if (!((Object)(object)health == (Object)null) && IsLocalPlayerHealth(health))
			{
				_lastKnownHealth = newHealth;
				RecomputeActivationState();
			}
		}

		private void SetupModUi()
		{
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Expected O, but got Unknown
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Expected O, but got Unknown
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Expected O, but got Unknown
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Expected O, but got Unknown
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Expected O, but got Unknown
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Expected O, but got Unknown
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Expected O, but got Unknown
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Expected O, but got Unknown
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Expected O, but got Unknown
			//IL_0138: Unknown result type (might be due to invalid IL or missing references)
			//IL_0142: Expected O, but got Unknown
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_014c: Expected O, but got Unknown
			if (!_modUiBuilt)
			{
				_modUi.ModName = "Fury of the Fallen";
				_modUi.ModVersion = "1.0.1";
				_modUi.SetFolder("FuryOfTheFallen");
				_modUi.AddDescription("Summary", "", "Uses SpeedEffects lines, tinted red, on low health.", new Tags
				{
					IsSummary = true
				});
				_settings.Clear();
				_settings.Add((ModSetting)_modUi.AddToList("Enable Fury Visual", true, 0, "Toggles the visual effect.", new Tags()));
				_settings.Add((ModSetting)_modUi.AddToList("Always On", false, 0, "When enabled, the effect is always active regardless of health.", new Tags()));
				_settings.Add((ModSetting)_modUi.AddToList("Fury Visual Activation Health", 1, "Turns on when health is less than or equal to this value.", new Tags()));
				_settings.Add((ModSetting)_modUi.AddToList("Overlay Strength", 0.85f, "Opacity of the red speed lines. 0 to 1.", new Tags()));
				_settings.Add((ModSetting)_modUi.AddToList("Panic Scale Amount", 0.15f, "How strong the scale is. 0 to 0.5", new Tags()));
				_modUi.GetFromFile();
				_modUi.ModSaved += SaveSettings;
				_modUiBuilt = true;
			}
		}

		private void OnUiInitialized()
		{
			SetupModUi();
			SaveSettings();
			TryRegisterModUi();
		}

		private void TrySubscribeUiInitialized()
		{
			try
			{
				if (UI.instance != null)
				{
					UI.instance.UI_Initialized -= OnUiInitialized;
					UI.instance.UI_Initialized += OnUiInitialized;
				}
			}
			catch (Exception)
			{
			}
		}

		private void TryRegisterModUi()
		{
			if (_modUiRegistered || !_modUiBuilt || UI.instance == null)
			{
				return;
			}
			try
			{
				UI.instance.AddMod(_modUi);
				_modUiRegistered = true;
				((MelonBase)this).LoggerInstance.Msg("Fury of the Fallen: registered in ModUI.");
			}
			catch (Exception)
			{
			}
		}

		private void SaveSettings()
		{
			if (_settings.Count >= 5)
			{
				_effectEnabled = ReadBool(_settings[0].SavedValue, fallback: true);
				_alwaysOn = ReadBool(_settings[1].SavedValue, fallback: false);
				_activationHealth = Mathf.Max(1, ReadInt(_settings[2].SavedValue, 1));
				_overlayStrength = Mathf.Clamp01(ReadFloat(_settings[3].SavedValue, 0.85f));
				_panicScaleStrength = Mathf.Clamp(ReadFloat(_settings[4].SavedValue, 0.15f), 0f, 0.5f);
				_settings[2].Value = _activationHealth;
				_settings[2].SavedValue = _activationHealth;
				_settings[3].Value = _overlayStrength;
				_settings[3].SavedValue = _overlayStrength;
				_settings[4].Value = _panicScaleStrength;
				_settings[4].SavedValue = _panicScaleStrength;
				RecomputeActivationState();
			}
		}

		private void RecomputeActivationState()
		{
			bool furyVisualShouldBeActive = _furyVisualShouldBeActive;
			bool flag = _lastKnownHealth > 0 && _lastKnownHealth <= _activationHealth;
			_furyVisualShouldBeActive = _effectEnabled && (_alwaysOn || flag);
			if (!_lastLoggedActivationState.HasValue || _lastLoggedActivationState.Value != _furyVisualShouldBeActive || furyVisualShouldBeActive != _furyVisualShouldBeActive)
			{
				((MelonBase)this).LoggerInstance.Msg($"Fury visual {(_furyVisualShouldBeActive ? "ENABLED" : "DISABLED")} | reason={GetActivationReason(flag)} | health={_lastKnownHealth} threshold={_activationHealth}");
				_lastLoggedActivationState = _furyVisualShouldBeActive;
			}
		}

		private string GetActivationReason(bool lowHealthActive)
		{
			if (!_effectEnabled)
			{
				return "setting_disabled";
			}
			if (_alwaysOn)
			{
				return "always_on";
			}
			if (!lowHealthActive)
			{
				return "health_above_threshold";
			}
			return "low_health";
		}

		private void RefreshPlayerHeadReference()
		{
			try
			{
				PlayerManager instance = Singleton<PlayerManager>.Instance;
				if ((Object)(object)instance == (Object)null || instance.localPlayer == null || (Object)(object)instance.localPlayer.Controller == (Object)null || (Object)(object)instance.localPlayer.Controller.PlayerCamera == (Object)null)
				{
					_playerHead = null;
				}
				else
				{
					_playerHead = ((Component)instance.localPlayer.Controller.PlayerCamera).transform;
				}
			}
			catch
			{
				_playerHead = null;
			}
		}

		private void UpdateHmdOverlayPose()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_furyHmdSpeedLinesOverlay == (Object)null) && !((Object)(object)_playerHead == (Object)null))
			{
				_furyHmdSpeedLinesOverlay.transform.position = _playerHead.position;
				_furyHmdSpeedLinesOverlay.transform.rotation = _playerHead.rotation * Quaternion.Euler(0f, -90f, 0f);
			}
		}

		private void EnsureSpeedLinesClone()
		{
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0146: Expected O, but got Unknown
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Expected O, but got Unknown
			if (_speedLinesReady && (Object)(object)_furySpeedLinesRoot != (Object)null && (Object)(object)_speedLinesMaterial != (Object)null && (Object)(object)_hmdSpeedLinesMaterial != (Object)null)
			{
				return;
			}
			GameObject val = GameObject.Find("Speed Lines");
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			try
			{
				_furySpeedLinesRoot = Object.Instantiate<GameObject>(val);
				((Object)_furySpeedLinesRoot).name = "Fury Speed Lines";
				Object.DontDestroyOnLoad((Object)(object)_furySpeedLinesRoot);
				_furySpeedLinesOverlay = ((Component)_furySpeedLinesRoot.transform.GetChild(0)).gameObject;
				_furyHmdSpeedLinesOverlay = ((Component)_furySpeedLinesRoot.transform.GetChild(1)).gameObject;
				_furyFlatBaseScale = _furySpeedLinesOverlay.transform.localScale;
				_furyHmdBaseScale = _furyHmdSpeedLinesOverlay.transform.localScale;
				Image component = ((Component)_furySpeedLinesOverlay.transform.GetChild(0)).GetComponent<Image>();
				MeshRenderer component2 = ((Component)_furyHmdSpeedLinesOverlay.transform.GetChild(0)).GetComponent<MeshRenderer>();
				if (!((Object)(object)component == (Object)null) && !((Object)(object)component2 == (Object)null) && !((Object)(object)((Graphic)component).material == (Object)null) && !((Object)(object)((Renderer)component2).material == (Object)null))
				{
					_speedLinesMaterial = new Material(((Graphic)component).material);
					_hmdSpeedLinesMaterial = new Material(((Renderer)component2).material);
					((Graphic)component).material = _speedLinesMaterial;
					((Renderer)component2).material = _hmdSpeedLinesMaterial;
					_speedLinesMaterial.SetFloat("_RenderSpace", 1f);
					((Component)_furyHmdSpeedLinesOverlay.transform.GetChild(0)).gameObject.layer = LayerMask.NameToLayer("PlayerFade");
					_speedLinesReady = true;
				}
			}
			catch (Exception)
			{
			}
		}

		private void ApplySpeedLinesVisuals(float alpha)
		{
			//IL_0179: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Unknown result type (might be due to invalid IL or missing references)
			if (_speedLinesReady && !((Object)(object)_speedLinesMaterial == (Object)null) && !((Object)(object)_hmdSpeedLinesMaterial == (Object)null))
			{
				bool flag = alpha > 0.001f;
				if (!_lastLoggedVisualVisibility.HasValue || _lastLoggedVisualVisibility.Value != flag)
				{
					((MelonBase)this).LoggerInstance.Msg($"Speed lines {(flag ? "ENABLED" : "DISABLED")} | alpha={alpha:0.000}");
					_lastLoggedVisualVisibility = flag;
				}
				if ((Object)(object)_furySpeedLinesOverlay != (Object)null)
				{
					_furySpeedLinesOverlay.SetActive(flag);
				}
				if ((Object)(object)_furyHmdSpeedLinesOverlay != (Object)null)
				{
					_furyHmdSpeedLinesOverlay.SetActive(flag);
				}
				if (!flag)
				{
					SetBothMaterials("_MaskScale", 10f);
					SetBothMaterials("_Colour", new Color(1f, 0f, 0f, 0f));
					ApplyPanicScale(1f);
				}
				else
				{
					float num = 0.5f + 0.5f * Mathf.Sin(_animatedPhase * 8.5f);
					float value = Mathf.Lerp(0.95f, 0.58f, num);
					SetBothMaterials("_MaskScale", value);
					SetBothMaterials("_Colour", new Color(1f, 0f, 0f, Mathf.Clamp01(alpha)));
					ApplyPanicScale(1f + _panicScaleStrength * (0.5f + 0.5f * Mathf.Sin(_animatedPhase * 8.5f)));
				}
			}
		}

		private void ApplyPanicScale(float multiplier)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_furySpeedLinesOverlay != (Object)null)
			{
				_furySpeedLinesOverlay.transform.localScale = _furyFlatBaseScale * multiplier;
			}
			if ((Object)(object)_furyHmdSpeedLinesOverlay != (Object)null)
			{
				_furyHmdSpeedLinesOverlay.transform.localScale = _furyHmdBaseScale * multiplier;
			}
		}

		private void SetBothMaterials(string property, float value)
		{
			_speedLinesMaterial.SetFloat(property, value);
			_hmdSpeedLinesMaterial.SetFloat(property, value * 0.2f);
		}

		private void SetBothMaterials(string property, Color value)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			_speedLinesMaterial.SetColor(property, value);
			_hmdSpeedLinesMaterial.SetColor(property, value);
		}

		private bool IsLocalPlayerHealth(PlayerHealth health)
		{
			try
			{
				PlayerManager instance = Singleton<PlayerManager>.Instance;
				if ((Object)(object)instance == (Object)null || instance.localPlayer == null || (Object)(object)instance.localPlayer.Controller == (Object)null)
				{
					return false;
				}
				PlayerHealth component = ((Component)instance.localPlayer.Controller).GetComponent<PlayerHealth>();
				return (Object)(object)component != (Object)null && (Object)(object)component == (Object)(object)health;
			}
			catch
			{
				return false;
			}
		}

		private void LoadHeartbeatAudio()
		{
			try
			{
				string text = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? string.Empty;
				string path = Path.GetDirectoryName(text) ?? text;
				string text2 = Path.Combine(path, "UserData", "FuryOfTheFallen");
				Directory.CreateDirectory(text2);
				string[] obj = new string[4]
				{
					Path.Combine(text, "Sounds"),
					Path.Combine(path, "Mods", "Sounds"),
					Path.Combine(path, "UserData", "FuryOfTheFallen", "Sounds"),
					"C:\\Users\\nanol\\Downloads\\Rumble Stuff\\Rumble Mod\\Fury of the Fallen\\Sounds"
				};
				string text3 = null;
				string[] array = obj;
				foreach (string path2 in array)
				{
					if (Directory.Exists(path2))
					{
						string[] files = Directory.GetFiles(path2, "*.wav");
						if (files.Length != 0)
						{
							text3 = files[0];
							break;
						}
						string[] files2 = Directory.GetFiles(path2, "*.mp3");
						if (files2.Length != 0)
						{
							text3 = files2[0];
							break;
						}
					}
				}
				if (text3 == null)
				{
					((MelonBase)this).LoggerInstance.Warning("Heartbeat source audio not found. Expected .wav or .mp3 in Sounds folder.");
					return;
				}
				string text4 = Path.Combine(text2, Path.GetFileName(text3));
				if (!File.Exists(text4))
				{
					File.Copy(text3, text4, overwrite: true);
					((MelonBase)this).LoggerInstance.Msg("Copied heartbeat audio to UserData: " + text4);
				}
				else
				{
					FileInfo fileInfo = new FileInfo(text3);
					FileInfo fileInfo2 = new FileInfo(text4);
					if (fileInfo.Length != fileInfo2.Length || fileInfo.LastWriteTimeUtc > fileInfo2.LastWriteTimeUtc)
					{
						File.Copy(text3, text4, overwrite: true);
						((MelonBase)this).LoggerInstance.Msg("Updated heartbeat audio in UserData: " + text4);
					}
				}
				_heartbeatPath = text4;
				_heartbeatRelativePath = Path.Combine("UserData", "FuryOfTheFallen", Path.GetFileName(text4));
				if (!IsValidWavFile(_heartbeatPath))
				{
					string text5 = Path.Combine(text2, "heartbeat_auto.wav");
					WriteFallbackHeartbeatWav(text5);
					_heartbeatPath = text5;
					_heartbeatRelativePath = Path.Combine("UserData", "FuryOfTheFallen", Path.GetFileName(text5));
					((MelonBase)this).LoggerInstance.Warning("Heartbeat source was not a valid WAV file. Generated fallback heartbeat_auto.wav.");
				}
				((MelonBase)this).LoggerInstance.Msg("Heartbeat file ready: " + _heartbeatPath);
			}
			catch (Exception ex)
			{
				((MelonBase)this).LoggerInstance.Warning("Heartbeat load failed: " + ex.Message);
			}
		}

		private static bool IsValidWavFile(string path)
		{
			try
			{
				if (!File.Exists(path))
				{
					return false;
				}
				using FileStream fileStream = File.OpenRead(path);
				if (fileStream.Length < 12)
				{
					return false;
				}
				byte[] array = new byte[12];
				if (fileStream.Read(array, 0, 12) < 12)
				{
					return false;
				}
				string @string = Encoding.ASCII.GetString(array, 0, 4);
				string string2 = Encoding.ASCII.GetString(array, 8, 4);
				return @string == "RIFF" && string2 == "WAVE";
			}
			catch
			{
				return false;
			}
		}

		private static void WriteFallbackHeartbeatWav(string path)
		{
			int num = Mathf.CeilToInt(17640f);
			short[] array = new short[num];
			AddPulse(array, 22050, 0.06f, 0.1f, 70f, 0.85f);
			AddPulse(array, 22050, 0.3f, 0.08f, 62f, 0.7f);
			Directory.CreateDirectory(Path.GetDirectoryName(path) ?? ".");
			using FileStream output = File.Create(path);
			using BinaryWriter binaryWriter = new BinaryWriter(output);
			int num2 = num * 2;
			int value = 44100;
			short value2 = 2;
			binaryWriter.Write(Encoding.ASCII.GetBytes("RIFF"));
			binaryWriter.Write(36 + num2);
			binaryWriter.Write(Encoding.ASCII.GetBytes("WAVE"));
			binaryWriter.Write(Encoding.ASCII.GetBytes("fmt "));
			binaryWriter.Write(16);
			binaryWriter.Write((short)1);
			binaryWriter.Write((short)1);
			binaryWriter.Write(22050);
			binaryWriter.Write(value);
			binaryWriter.Write(value2);
			binaryWriter.Write((short)16);
			binaryWriter.Write(Encoding.ASCII.GetBytes("data"));
			binaryWriter.Write(num2);
			for (int i = 0; i < num; i++)
			{
				binaryWriter.Write(array[i]);
			}
		}

		private static void AddPulse(short[] buffer, int sampleRate, float startSeconds, float lengthSeconds, float frequency, float amplitude)
		{
			int num = Mathf.Max(0, Mathf.FloorToInt(startSeconds * (float)sampleRate));
			int num2 = Mathf.Max(1, Mathf.FloorToInt(lengthSeconds * (float)sampleRate));
			int num3 = Mathf.Min(buffer.Length, num + num2);
			for (int i = num; i < num3; i++)
			{
				float num4 = (float)(i - num) / (float)num2;
				float num5 = 1f - num4;
				float num6 = Mathf.Sin(2f * Mathf.PI * frequency * ((float)(i - num) / (float)sampleRate)) * num5 * amplitude;
				int num7 = buffer[i] + Mathf.RoundToInt(num6 * 32767f * 0.55f);
				buffer[i] = (short)Mathf.Clamp(num7, -32768, 32767);
			}
		}

		private void EnsureHeartbeatLoop()
		{
			if (!_heartbeatLoopRunning && _heartbeatCall != null && !(_audioPlaySoundMethod == null))
			{
				_heartbeatLoopRunning = true;
				MelonCoroutines.Start(HeartbeatLoop());
			}
		}

		[IteratorStateMachine(typeof(<HeartbeatLoop>d__74))]
		private IEnumerator HeartbeatLoop()
		{
			//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
			return new <HeartbeatLoop>d__74(0)
			{
				<>4__this = this
			};
		}

		private void StopHeartbeat()
		{
			if (_heartbeatSource != null)
			{
				try
				{
					_heartbeatSource.GetType().GetMethod("ReturnToPool", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.Invoke(_heartbeatSource, null);
				}
				catch
				{
				}
				_heartbeatSource = null;
			}
			_heartbeatLoopRunning = false;
		}

		private void EnsureHeartbeatAudioCall()
		{
			if (_heartbeatCall != null || Time.unscaledTime < _nextHeartbeatInitTryTime)
			{
				return;
			}
			_nextHeartbeatInitTryTime = Time.unscaledTime + 2f;
			if (string.IsNullOrWhiteSpace(_heartbeatPath) || !File.Exists(_heartbeatPath))
			{
				LoadHeartbeatAudio();
				if (string.IsNullOrWhiteSpace(_heartbeatPath) || !File.Exists(_heartbeatPath))
				{
					((MelonBase)this).LoggerInstance.Warning("Heartbeat missing: no audio file in UserData\\FuryOfTheFallen.");
					return;
				}
			}
			CacheAudioManagerMethods();
			if (_audioCreateCallMethod == null)
			{
				((MelonBase)this).LoggerInstance.Warning("Heartbeat unavailable: no compatible CreateAudioCall method found yet.");
				return;
			}
			if (_audioPlaySoundMethod == null)
			{
				((MelonBase)this).LoggerInstance.Warning("Heartbeat unavailable: no compatible PlaySound method found yet.");
				return;
			}
			try
			{
				string text = ((!string.IsNullOrWhiteSpace(_heartbeatRelativePath)) ? _heartbeatRelativePath : _heartbeatPath);
				object[] parameters = BuildCreateAudioCallArgs(_audioCreateCallMethod, text);
				_heartbeatCall = _audioCreateCallMethod.Invoke(null, parameters);
				if (_heartbeatCall == null && !string.Equals(text, _heartbeatPath, StringComparison.OrdinalIgnoreCase))
				{
					object[] parameters2 = BuildCreateAudioCallArgs(_audioCreateCallMethod, _heartbeatPath);
					_heartbeatCall = _audioCreateCallMethod.Invoke(null, parameters2);
				}
				if (_heartbeatCall == null)
				{
					((MelonBase)this).LoggerInstance.Warning($"Heartbeat create call returned null for: {text} and {_heartbeatPath}. Try .wav if .mp3 fails.");
				}
				else
				{
					((MelonBase)this).LoggerInstance.Msg("Heartbeat audio loaded: " + text);
				}
			}
			catch (TargetInvocationException ex) when (ex.InnerException != null)
			{
				((MelonBase)this).LoggerInstance.Warning("Heartbeat create inner failure: " + ex.InnerException.GetType().Name + ": " + ex.InnerException.Message);
			}
			catch (Exception ex2)
			{
				((MelonBase)this).LoggerInstance.Warning("Heartbeat create call failed: " + ex2.Message);
			}
		}

		private void CacheAudioManagerMethods()
		{
			if (_audioCreateCallMethod != null && _audioPlaySoundMethod != null)
			{
				return;
			}
			List<MethodInfo> list = new List<MethodInfo>();
			List<MethodInfo> list2 = new List<MethodInfo>();
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				Type[] array;
				try
				{
					array = assembly.GetTypes();
				}
				catch (ReflectionTypeLoadException ex)
				{
					array = Array.FindAll(ex.Types, (Type t) => t != null);
				}
				Type[] array2 = array;
				foreach (Type type in array2)
				{
					if (type == null || type.Name.IndexOf("AudioManager", StringComparison.OrdinalIgnoreCase) < 0)
					{
						continue;
					}
					MethodInfo[] methods;
					try
					{
						methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
					}
					catch
					{
						continue;
					}
					MethodInfo[] array3 = methods;
					foreach (MethodInfo methodInfo in array3)
					{
						ParameterInfo[] parameters;
						Type returnType;
						try
						{
							parameters = methodInfo.GetParameters();
							returnType = methodInfo.ReturnType;
						}
						catch
						{
							continue;
						}
						if (parameters.Length != 0)
						{
							if (methodInfo.Name.IndexOf("CreateAudioCall", StringComparison.OrdinalIgnoreCase) >= 0 && parameters[0].ParameterType == typeof(string) && returnType != typeof(void))
							{
								list.Add(methodInfo);
							}
							else if (methodInfo.Name.IndexOf("PlaySound", StringComparison.OrdinalIgnoreCase) >= 0 && Array.Exists(parameters, (ParameterInfo a) => a.ParameterType == typeof(Vector3)))
							{
								list2.Add(methodInfo);
							}
						}
					}
				}
			}
			MethodInfo methodInfo2 = null;
			MethodInfo audioPlaySoundMethod = null;
			List<MethodInfo> list3 = list.FindAll((MethodInfo m) => m.DeclaringType != null && m.DeclaringType.FullName != null && m.DeclaringType.FullName.IndexOf("Il2CppRUMBLE.Audio.AudioManager", StringComparison.OrdinalIgnoreCase) >= 0);
			List<MethodInfo> list4 = list2.FindAll((MethodInfo m) => m.DeclaringType != null && m.DeclaringType.FullName != null && m.DeclaringType.FullName.IndexOf("Il2CppRUMBLE.Audio.AudioManager", StringComparison.OrdinalIgnoreCase) >= 0);
			foreach (MethodInfo item in (list3.Count > 0) ? list3 : list)
			{
				foreach (MethodInfo item2 in (list4.Count > 0) ? list4 : list2)
				{
					if (!(item2.DeclaringType != item.DeclaringType))
					{
						ParameterInfo[] parameters2;
						try
						{
							parameters2 = item2.GetParameters();
						}
						catch
						{
							continue;
						}
						if (parameters2.Length != 0 && parameters2[0].ParameterType == item.ReturnType)
						{
							methodInfo2 = item;
							audioPlaySoundMethod = item2;
							break;
						}
					}
				}
				if (methodInfo2 != null)
				{
					break;
				}
			}
			if (methodInfo2 == null)
			{
				foreach (MethodInfo item3 in (list3.Count > 0) ? list3 : list)
				{
					foreach (MethodInfo item4 in (list4.Count > 0) ? list4 : list2)
					{
						if (!(item4.DeclaringType != item3.DeclaringType))
						{
							ParameterInfo[] parameters3;
							try
							{
								parameters3 = item4.GetParameters();
							}
							catch
							{
								continue;
							}
							if (parameters3.Length != 0 && parameters3[0].ParameterType.Name.IndexOf("AudioCall", StringComparison.OrdinalIgnoreCase) >= 0)
							{
								methodInfo2 = item3;
								audioPlaySoundMethod = item4;
								break;
							}
						}
					}
					if (methodInfo2 != null)
					{
						break;
					}
				}
			}
			if (methodInfo2 == null && list.Count > 0)
			{
				methodInfo2 = ((list3.Count > 0) ? list3[0] : list[0]);
				foreach (MethodInfo item5 in (list4.Count > 0) ? list4 : list2)
				{
					ParameterInfo[] parameters4;
					try
					{
						parameters4 = item5.GetParameters();
					}
					catch
					{
						continue;
					}
					if (parameters4.Length != 0 && parameters4[0].ParameterType.Name.IndexOf("AudioCall", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						audioPlaySoundMethod = item5;
						break;
					}
				}
			}
			_audioCreateCallMethod = methodInfo2;
			_audioPlaySoundMethod = audioPlaySoundMethod;
			if (_audioCreateCallMethod != null)
			{
				((MelonBase)this).LoggerInstance.Msg("Heartbeat found create method: " + _audioCreateCallMethod.DeclaringType?.FullName + "." + _audioCreateCallMethod.Name);
			}
			if (_audioPlaySoundMethod != null)
			{
				((MelonBase)this).LoggerInstance.Msg("Heartbeat found play method: " + _audioPlaySoundMethod.DeclaringType?.FullName + "." + _audioPlaySoundMethod.Name);
			}
		}

		private object[] BuildCreateAudioCallArgs(MethodInfo method, string path)
		{
			ParameterInfo[] parameters = method.GetParameters();
			object[] array = new object[parameters.Length];
			for (int i = 0; i < parameters.Length; i++)
			{
				Type parameterType = parameters[i].ParameterType;
				if (i == 0 && parameterType == typeof(string))
				{
					array[i] = path;
				}
				else if (parameterType == typeof(float))
				{
					array[i] = 0.8f;
				}
				else if (parameterType == typeof(bool))
				{
					array[i] = false;
				}
				else
				{
					array[i] = (parameters[i].HasDefaultValue ? parameters[i].DefaultValue : GetDefault(parameterType));
				}
			}
			return array;
		}

		private object[] BuildPlaySoundArgs(MethodInfo method, object audioCall, Vector3 position)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			ParameterInfo[] parameters = method.GetParameters();
			object[] array = new object[parameters.Length];
			for (int i = 0; i < parameters.Length; i++)
			{
				Type parameterType = parameters[i].ParameterType;
				if (i == 0)
				{
					array[i] = audioCall;
				}
				else if (parameterType == typeof(Vector3))
				{
					array[i] = position;
				}
				else if (parameterType == typeof(bool))
				{
					array[i] = false;
				}
				else if (parameterType == typeof(float))
				{
					array[i] = 0.8f;
				}
				else
				{
					array[i] = (parameters[i].HasDefaultValue ? parameters[i].DefaultValue : GetDefault(parameterType));
				}
			}
			return array;
		}

		private static object GetDefault(Type type)
		{
			if (!type.IsValueType)
			{
				return null;
			}
			return Activator.CreateInstance(type);
		}

		private static int ReadInt(object value, int fallback)
		{
			if (value is int)
			{
				return (int)value;
			}
			if (value is float num)
			{
				return Mathf.RoundToInt(num);
			}
			if (!int.TryParse(value?.ToString(), out var result))
			{
				return fallback;
			}
			return result;
		}

		private static float ReadFloat(object value, float fallback)
		{
			if (value is float)
			{
				return (float)value;
			}
			if (value is int num)
			{
				return num;
			}
			if (!float.TryParse(value?.ToString(), out var result))
			{
				return fallback;
			}
			return result;
		}

		private static bool ReadBool(object value, bool fallback)
		{
			if (value is bool)
			{
				return (bool)value;
			}
			if (!bool.TryParse(value?.ToString(), out var result))
			{
				return fallback;
			}
			return result;
		}
	}
	[HarmonyPatch(typeof(PlayerHealth), "SetHealth", new Type[]
	{
		typeof(short),
		typeof(short),
		typeof(bool)
	})]
	internal static class LocalHealthPatch
	{
		private static void Postfix(ref PlayerHealth __instance, short newHealth, short previousHealth, bool useEffects)
		{
			FuryOfTheFallenMod.Instance?.HandleHealthChanged(__instance, newHealth, previousHealth, useEffects);
		}
	}
}