Decompiled source of FatalFall v0.5.10

FatalFall.dll

Decompiled 2 days 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 BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.Networking;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.0.0.0")]
namespace Pix.FatalFall;

[BepInPlugin("Pix.FatalFall", "Fatal Fall", "0.5.10")]
public sealed class FatalFallPlugin : BaseUnityPlugin
{
	[CompilerGenerated]
	private sealed class <ClientEnsureSeRoutine>d__47 : IEnumerator<object>, IEnumerator, IDisposable
	{
		private int <>1__state;

		private object <>2__current;

		public FatalFallPlugin <>4__this;

		private int <i>5__1;

		private ObjectDB <odb>5__2;

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

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

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

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

		private bool MoveNext()
		{
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Expected O, but got Unknown
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<i>5__1 = 0;
				break;
			case 1:
				<>1__state = -1;
				<i>5__1++;
				break;
			}
			if (<i>5__1 < 20)
			{
				try
				{
					<odb>5__2 = ObjectDB.instance;
					if ((Object)(object)<odb>5__2 != (Object)null)
					{
						EnsureBleedIconLoaded((Object)(object)BleedIconSprite == (Object)null);
						Patches.EnsureBleedRegistered(<odb>5__2, forceIconOverwrite: true);
						if ((Object)(object)BleedPrefab != (Object)null && (Object)(object)BleedPrefab.m_icon != (Object)null)
						{
							Dbg("ClientEnsureSeRoutine: Bleed SE ready (prefab+icon).");
							return false;
						}
					}
					<odb>5__2 = null;
				}
				catch
				{
				}
				<>2__current = (object)new WaitForSeconds(1f);
				<>1__state = 1;
				return true;
			}
			Dbg("ClientEnsureSeRoutine: Bleed SE not confirmed ready after retries (prefab/icon missing).");
			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();
		}
	}

	[CompilerGenerated]
	private sealed class <LoadHeartbeatOgg>d__43 : IEnumerator<object>, IEnumerator, IDisposable
	{
		private int <>1__state;

		private object <>2__current;

		public FatalFallPlugin <>4__this;

		private string <path>5__1;

		private string <uri>5__2;

		private UnityWebRequest <req>5__3;

		private string <dllDir>5__4;

		private Exception <e>5__5;

		private Exception <e>5__6;

		private AudioClip <clip>5__7;

		private Exception <e>5__8;

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

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

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

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<path>5__1 = null;
			<uri>5__2 = null;
			<req>5__3 = null;
			<dllDir>5__4 = null;
			<e>5__5 = null;
			<e>5__6 = null;
			<clip>5__7 = null;
			<e>5__8 = null;
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			//IL_0170: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Invalid comparison between Unknown and I4
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<path>5__1 = null;
				<uri>5__2 = null;
				try
				{
					<dllDir>5__4 = Path.GetDirectoryName(((BaseUnityPlugin)<>4__this).Info.Location);
					<path>5__1 = Path.Combine(<dllDir>5__4 ?? ".", _soundFileName.Value ?? "heartbeat.ogg");
					if (!File.Exists(<path>5__1))
					{
						Dbg("Sound file not found: " + <path>5__1);
						return false;
					}
					<uri>5__2 = new Uri(<path>5__1).AbsoluteUri;
					<dllDir>5__4 = null;
				}
				catch (Exception ex)
				{
					<e>5__5 = ex;
					Dbg("OGG path exception: " + <e>5__5.GetType().Name);
					return false;
				}
				<req>5__3 = null;
				try
				{
					<req>5__3 = UnityWebRequestMultimedia.GetAudioClip(<uri>5__2, (AudioType)14);
				}
				catch (Exception ex)
				{
					<e>5__6 = ex;
					Dbg("OGG request create exception: " + <e>5__6.GetType().Name);
					return false;
				}
				<>2__current = <req>5__3.SendWebRequest();
				<>1__state = 1;
				return true;
			case 1:
				<>1__state = -1;
				try
				{
					if ((int)<req>5__3.result != 1)
					{
						Dbg("OGG load failed: " + <req>5__3.error);
						return false;
					}
					<clip>5__7 = DownloadHandlerAudioClip.GetContent(<req>5__3);
					if ((Object)(object)<clip>5__7 == (Object)null)
					{
						Dbg("OGG load failed: clip was null.");
						return false;
					}
					HeartbeatClip = <clip>5__7;
					Dbg($"OGG loaded len={<clip>5__7.length:0.00}s from={Path.GetFileName(<path>5__1)}");
					<clip>5__7 = null;
				}
				catch (Exception ex)
				{
					<e>5__8 = ex;
					Dbg("OGG parse exception: " + <e>5__8.GetType().Name);
				}
				finally
				{
					<req>5__3.Dispose();
				}
				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();
		}
	}

	public const string PluginGuid = "Pix.FatalFall";

	public const string PluginName = "Fatal Fall";

	public const string PluginVersion = "0.5.10";

	internal static FatalFallPlugin Instance;

	internal static ManualLogSource LogSrc;

	private static ConfigEntry<bool> _enabled;

	private static ConfigEntry<float> _fallMultiplier;

	private static ConfigEntry<bool> _borderlineEnabled;

	private static ConfigEntry<float> _borderlineWindowHp;

	private static ConfigEntry<float> _leaveHp;

	private static ConfigEntry<float> _maxOverkillToSave;

	private static ConfigEntry<bool> _borderlineAllDamage;

	private static ConfigEntry<float> _borderlineCooldownSeconds;

	private static ConfigEntry<bool> _borderlineSkipIfBubble;

	private static ConfigEntry<bool> _bleedEnabled;

	private static ConfigEntry<float> _bleedDuration;

	private static ConfigEntry<float> _bleedDps;

	private static ConfigEntry<float> _bleedMinHp;

	private static ConfigEntry<bool> _bleedSkipIfBubble;

	private static readonly int[] _bubbleSeHashes = new int[5]
	{
		StableHash.GetStableHashCode("SE_Shield"),
		StableHash.GetStableHashCode("SE_StaffShield"),
		StableHash.GetStableHashCode("SE_StaffShield_Bubble"),
		StableHash.GetStableHashCode("SE_StaffShield_Protection"),
		StableHash.GetStableHashCode("SE_StaffShieldProtection")
	};

	private static ConfigEntry<bool> _popupEnabled;

	private static ConfigEntry<string> _popupLines;

	private static ConfigEntry<string> _popupLinesGeneric;

	private static ConfigEntry<bool> _soundEnabled;

	private static ConfigEntry<string> _soundFileName;

	private static ConfigEntry<bool> _staggerEnabled;

	private static ConfigEntry<float> _staggerStrength;

	private static ConfigEntry<bool> _debug;

	private static ConfigEntry<bool> _uncapEnabled;

	private static ConfigEntry<float> _uncapStartHeight;

	private static ConfigEntry<float> _uncapDamagePerMeter;

	private static ConfigEntry<float> _uncapMaxExtraDamage;

	private static ConfigEntry<float> _uncapRequireRecentFallHitWindow;

	internal const string BleedSeName = "SE_PixFatalFall_Bleed";

	internal static readonly int BleedSeHash = StableHash.GetStableHashCode("SE_PixFatalFall_Bleed");

	internal static Sprite BleedIconSprite;

	internal static AudioClip HeartbeatClip;

	internal static StatusEffect BleedPrefab;

	private static GameObject _audioGo;

	private static AudioSource _audioSource;

	private Harmony _harmony;

	private void Awake()
	{
		//IL_03f9: Unknown result type (might be due to invalid IL or missing references)
		//IL_0403: Expected O, but got Unknown
		Instance = this;
		LogSrc = ((BaseUnityPlugin)this).Logger;
		_enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enabled", true, "Enable the mod.");
		_fallMultiplier = ((BaseUnityPlugin)this).Config.Bind<float>("General", "FallDamageMultiplier", 1f, "Multiplier applied to fall damage before borderline logic.");
		_debug = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "DebugLogging", false, "Enable detailed logs.");
		_borderlineEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Borderline", "Enabled", true, "Enable borderline survival behavior.");
		_borderlineWindowHp = ((BaseUnityPlugin)this).Config.Bind<float>("Borderline", "BorderlineWindowHp", 13f, "If (incomingDamage - currentHP) <= this window, the mod may save you.");
		_leaveHp = ((BaseUnityPlugin)this).Config.Bind<float>("Borderline", "LeaveHp", 1f, "HP to leave the character at when saved.");
		_maxOverkillToSave = ((BaseUnityPlugin)this).Config.Bind<float>("Borderline", "MaxOverkillToSave", 50f, "If (incomingDamage - currentHP) > this, do NOT save (die normally).");
		_borderlineAllDamage = ((BaseUnityPlugin)this).Config.Bind<bool>("Borderline", "ApplyToAllDamage", true, "If true, borderline save can trigger on any near-fatal hit (mobs, DoT, etc). If false, only fall damage can trigger it.");
		_borderlineCooldownSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Borderline", "CooldownSeconds", 60f, "Borderline cannot trigger again for this many seconds after a save (prevents chain-proc cheese).");
		_borderlineSkipIfBubble = ((BaseUnityPlugin)this).Config.Bind<bool>("Borderline", "SkipIfStaffBubbleActive", true, "If true, borderline save logic will not arm while a Staff of Protection bubble/shield status is active. Prevents false procs caused by raw damage being evaluated before bubble absorption.");
		_popupEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "PopupEnabled", true, "Show a center-screen message on borderline saves.");
		_popupLines = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "PopupLines_Fall", "That fall was nearly fatal.|Your bones shake in despair.|You barely survive.|The ground almost claims you.", "Pipe-separated popup lines for FALL borderline saves.");
		_popupLinesGeneric = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "PopupLines_Generic", "That was nearly fatal.|You barely survive.|Death brushes past you.|You stagger but stand.", "Pipe-separated popup lines for NON-FALL borderline saves.");
		_soundEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Sound", "Enabled", true, "Play a sound on borderline saves.");
		_soundFileName = ((BaseUnityPlugin)this).Config.Bind<string>("Sound", "OggFileName", "heartbeat.ogg", "OGG filename placed next to the plugin DLL.");
		_bleedEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Bleed", "Enabled", true, "Apply a bleed status effect after borderline saves.");
		_bleedDuration = ((BaseUnityPlugin)this).Config.Bind<float>("Bleed", "DurationSeconds", 45f, "Bleed duration in seconds.");
		_bleedDps = ((BaseUnityPlugin)this).Config.Bind<float>("Bleed", "Dps", 1.25f, "Bleed damage per second.");
		_bleedMinHp = ((BaseUnityPlugin)this).Config.Bind<float>("Bleed", "MinHp", 1f, "Bleed will not reduce the character below this HP.");
		_bleedSkipIfBubble = ((BaseUnityPlugin)this).Config.Bind<bool>("Bleed", "SkipIfStaffBubbleActive", true, "If true, bleed will not be applied while a Staff of Protection bubble/shield status is active (best-effort detection).");
		_staggerEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Stagger", "Enabled", true, "Apply a stagger on borderline saves.");
		_staggerStrength = ((BaseUnityPlugin)this).Config.Bind<float>("Stagger", "Strength", 1f, "Stagger strength scalar.");
		_uncapEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Uncap", "Enabled", false, "If true, allows falls to exceed vanilla's effective cap by adding extra landing damage on touchdown (safe, server-authoritative).");
		_uncapStartHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Uncap", "StartHeightMeters", 16f, "Extra damage starts after this many meters of fall height (approx).");
		_uncapDamagePerMeter = ((BaseUnityPlugin)this).Config.Bind<float>("Uncap", "ExtraDamagePerMeter", 20f, "Extra damage added per meter beyond StartHeightMeters.");
		_uncapMaxExtraDamage = ((BaseUnityPlugin)this).Config.Bind<float>("Uncap", "MaxExtraDamage", 500f, "Hard clamp for added extra landing damage to prevent runaway values.");
		_uncapRequireRecentFallHitWindow = ((BaseUnityPlugin)this).Config.Bind<float>("Uncap", "RequireRecentFallHitSeconds", 0.35f, "Safety: extra landing damage only applies if we observed a real fall Hit within this many seconds.");
		DamageTypesScaler.Init();
		HitIntrospection.Init();
		EnsureBleedIconLoaded(forceReload: false);
		if (!Application.isBatchMode)
		{
			EnsureAudioSource();
			((MonoBehaviour)this).StartCoroutine(LoadHeartbeatOgg());
			((MonoBehaviour)this).StartCoroutine(ClientEnsureSeRoutine());
		}
		_harmony = new Harmony("Pix.FatalFall");
		_harmony.PatchAll(typeof(Patches));
		_harmony.PatchAll(typeof(FallTrackerPatches));
		Dbg(string.Format("Boot {0} | BleedSE='{1}' hash={2}", "0.5.10", "SE_PixFatalFall_Bleed", BleedSeHash));
	}

	private static void EnsureAudioSource()
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Expected O, but got Unknown
		if (!((Object)(object)_audioSource != (Object)null))
		{
			_audioGo = new GameObject("PixFatalFallAudio");
			Object.DontDestroyOnLoad((Object)(object)_audioGo);
			_audioSource = _audioGo.AddComponent<AudioSource>();
			_audioSource.playOnAwake = false;
			_audioSource.loop = false;
			_audioSource.spatialBlend = 0f;
			_audioSource.volume = 1f;
		}
	}

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

	private static string GetDllDirSafe()
	{
		try
		{
			if ((Object)(object)Instance != (Object)null && ((BaseUnityPlugin)Instance).Info != null && !string.IsNullOrEmpty(((BaseUnityPlugin)Instance).Info.Location))
			{
				return Path.GetDirectoryName(((BaseUnityPlugin)Instance).Info.Location);
			}
		}
		catch
		{
		}
		try
		{
			return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
		}
		catch
		{
			return null;
		}
	}

	internal static void EnsureBleedIconLoaded(bool forceReload)
	{
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Expected O, but got Unknown
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_009e: Unknown result type (might be due to invalid IL or missing references)
		if (!forceReload && (Object)(object)BleedIconSprite != (Object)null)
		{
			return;
		}
		try
		{
			string dllDirSafe = GetDllDirSafe();
			if (!string.IsNullOrEmpty(dllDirSafe))
			{
				string text = Path.Combine(dllDirSafe, "bleed.png");
				if (File.Exists(text))
				{
					byte[] array = File.ReadAllBytes(text);
					Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
					if (ImageConversion.LoadImage(val, array))
					{
						BleedIconSprite = Sprite.Create(val, new Rect(0f, 0f, (float)((Texture)val).width, (float)((Texture)val).height), new Vector2(0.5f, 0.5f));
						Dbg($"bleed.png loaded OK ({((Texture)val).width}x{((Texture)val).height}) from='{dllDirSafe}'");
						return;
					}
					Dbg("bleed.png exists but failed to decode from='" + dllDirSafe + "'");
				}
				else
				{
					Dbg("bleed.png not found next to dll: '" + text + "'");
				}
			}
		}
		catch (Exception ex)
		{
			Dbg("bleed.png load exception: " + ex.GetType().Name);
		}
		BleedIconSprite = TryGetFallbackIcon();
	}

	private static Sprite TryGetFallbackIcon()
	{
		try
		{
			ObjectDB instance = ObjectDB.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return null;
			}
			StatusEffect statusEffect = instance.GetStatusEffect(StableHash.GetStableHashCode("Wet"));
			if ((Object)(object)statusEffect != (Object)null && (Object)(object)statusEffect.m_icon != (Object)null)
			{
				return statusEffect.m_icon;
			}
			StatusEffect statusEffect2 = instance.GetStatusEffect(StableHash.GetStableHashCode("SE_FeatherFall"));
			if ((Object)(object)statusEffect2 != (Object)null && (Object)(object)statusEffect2.m_icon != (Object)null)
			{
				return statusEffect2.m_icon;
			}
		}
		catch
		{
		}
		return null;
	}

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

	internal static bool DebugEnabled()
	{
		try
		{
			return _debug != null && _debug.Value;
		}
		catch
		{
			return false;
		}
	}

	internal static void Dbg(string msg)
	{
		if (DebugEnabled() && LogSrc != null)
		{
			LogSrc.LogInfo((object)("[Pix.FatalFall] " + msg));
		}
	}

	internal static float Now()
	{
		try
		{
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return (float)ZNet.instance.GetTimeSeconds();
			}
		}
		catch
		{
		}
		return Time.realtimeSinceStartup;
	}

	internal static bool IsEnabled()
	{
		return _enabled.Value;
	}

	internal static float GetMul()
	{
		return Mathf.Max(0f, _fallMultiplier.Value);
	}

	internal static bool BorderlineEnabled()
	{
		return _borderlineEnabled.Value;
	}

	internal static bool BorderlineAllDamage()
	{
		return _borderlineAllDamage.Value;
	}

	internal static float BorderlineWindowHp()
	{
		return Mathf.Max(0f, _borderlineWindowHp.Value);
	}

	internal static float LeaveHp()
	{
		return Mathf.Clamp(_leaveHp.Value, 0f, 9999f);
	}

	internal static float MaxOverkillToSave()
	{
		return Mathf.Max(0f, _maxOverkillToSave.Value);
	}

	internal static float BorderlineCooldownSeconds()
	{
		return Mathf.Clamp(_borderlineCooldownSeconds.Value, 0f, 9999f);
	}

	internal static bool BorderlineSkipIfBubble()
	{
		return _borderlineSkipIfBubble != null && _borderlineSkipIfBubble.Value;
	}

	internal static bool PopupEnabled()
	{
		return _popupEnabled.Value;
	}

	internal static string[] PopupChoicesFall()
	{
		string text = _popupLines.Value ?? "";
		string[] array = text.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
		if (array.Length == 0)
		{
			return new string[1] { "That fall was nearly fatal." };
		}
		return array;
	}

	internal static string[] PopupChoicesGeneric()
	{
		string text = _popupLinesGeneric.Value ?? "";
		string[] array = text.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
		if (array.Length == 0)
		{
			return new string[1] { "That was nearly fatal." };
		}
		return array;
	}

	internal static bool SoundEnabled()
	{
		return _soundEnabled.Value;
	}

	internal static bool BleedEnabled()
	{
		return _bleedEnabled.Value;
	}

	internal static float BleedDuration()
	{
		return Mathf.Clamp(_bleedDuration.Value, 0f, 9999f);
	}

	internal static float BleedDps()
	{
		return Mathf.Clamp(_bleedDps.Value, 0f, 9999f);
	}

	internal static float BleedMinHp()
	{
		return Mathf.Clamp(_bleedMinHp.Value, 0f, 9999f);
	}

	internal static bool BleedSkipIfBubble()
	{
		return _bleedSkipIfBubble.Value;
	}

	internal static bool StaggerEnabled()
	{
		return _staggerEnabled.Value;
	}

	internal static float StaggerStrength()
	{
		return Mathf.Clamp(_staggerStrength.Value, 0f, 10f);
	}

	internal static bool UncapEnabled()
	{
		return _uncapEnabled.Value;
	}

	internal static float UncapStartHeight()
	{
		return Mathf.Clamp(_uncapStartHeight.Value, 0f, 9999f);
	}

	internal static float UncapDamagePerMeter()
	{
		return Mathf.Clamp(_uncapDamagePerMeter.Value, 0f, 9999f);
	}

	internal static float UncapMaxExtraDamage()
	{
		return Mathf.Clamp(_uncapMaxExtraDamage.Value, 0f, 99999f);
	}

	internal static float UncapRequireRecentFallHitWindow()
	{
		return Mathf.Clamp(_uncapRequireRecentFallHitWindow.Value, 0.05f, 5f);
	}

	internal static void PlayHeartbeat()
	{
		if (!Application.isBatchMode && SoundEnabled() && !((Object)(object)HeartbeatClip == (Object)null))
		{
			if ((Object)(object)_audioSource == (Object)null)
			{
				EnsureAudioSource();
			}
			if (!((Object)(object)_audioSource == (Object)null))
			{
				_audioSource.PlayOneShot(HeartbeatClip);
			}
		}
	}

	internal static bool HasStaffBubble(Character ch)
	{
		if ((Object)(object)ch == (Object)null)
		{
			return false;
		}
		try
		{
			SEMan sEMan = ch.GetSEMan();
			if (sEMan == null)
			{
				return false;
			}
			for (int i = 0; i < _bubbleSeHashes.Length; i++)
			{
				if (sEMan.HaveStatusEffect(_bubbleSeHashes[i]))
				{
					return true;
				}
			}
		}
		catch
		{
		}
		return false;
	}
}
internal static class StableHash
{
	public static int GetStableHashCode(string str)
	{
		int num = 5381;
		int num2 = num;
		for (int i = 0; i < str.Length && str[i] != 0; i += 2)
		{
			num = ((num << 5) + num) ^ str[i];
			if (i == str.Length - 1 || str[i + 1] == '\0')
			{
				break;
			}
			num2 = ((num2 << 5) + num2) ^ str[i + 1];
		}
		return num + num2 * 1566083941;
	}
}
internal static class HitIntrospection
{
	private static bool _inited;

	private static FieldInfo _fallField;

	private static FieldInfo _hitTypeField;

	public static void Init()
	{
		if (!_inited)
		{
			_inited = true;
			try
			{
				_fallField = typeof(DamageTypes).GetField("m_fall", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			}
			catch
			{
				_fallField = null;
			}
			try
			{
				_hitTypeField = typeof(HitData).GetField("m_hitType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			}
			catch
			{
				_hitTypeField = null;
			}
			FatalFallPlugin.Dbg($"Introspection fallFieldFound={_fallField != null} hitTypeFieldFound={_hitTypeField != null}");
		}
	}

	public static bool IsFallHit(HitData hit)
	{
		if (hit == null)
		{
			return false;
		}
		float fallDamage = GetFallDamage(hit);
		if (fallDamage > 0.0001f)
		{
			return true;
		}
		try
		{
			if (_hitTypeField != null)
			{
				object value = _hitTypeField.GetValue(hit);
				if (value != null)
				{
					string text = value.ToString();
					if (!string.IsNullOrEmpty(text) && text.IndexOf("Fall", StringComparison.OrdinalIgnoreCase) >= 0)
					{
						return true;
					}
				}
			}
		}
		catch
		{
		}
		return false;
	}

	public static float GetFallDamage(HitData hit)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		if (hit == null)
		{
			return 0f;
		}
		try
		{
			if (_fallField != null)
			{
				object value = _fallField.GetValue(hit.m_damage);
				if (value is float)
				{
					float result = (float)value;
					if (true)
					{
						return result;
					}
				}
			}
		}
		catch
		{
		}
		return 0f;
	}

	public static void SetFallDamage(ref HitData hit, float value)
	{
		//IL_0020: Unknown result type (might be due to invalid IL or missing references)
		//IL_0025: Unknown result type (might be due to invalid IL or missing references)
		//IL_0040: Unknown result type (might be due to invalid IL or missing references)
		//IL_0041: Unknown result type (might be due to invalid IL or missing references)
		if (hit == null)
		{
			return;
		}
		try
		{
			if (_fallField != null)
			{
				DamageTypes damage = hit.m_damage;
				_fallField.SetValueDirect(__makeref(damage), value);
				hit.m_damage = damage;
			}
		}
		catch
		{
		}
	}

	public static float SumAllDamage(HitData hit)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		if (hit == null)
		{
			return 0f;
		}
		return DamageTypesScaler.Sum(hit.m_damage);
	}
}
internal static class DamageTypesScaler
{
	private static FieldInfo[] _floatFields;

	private static bool _inited;

	public static void Init()
	{
		if (_inited)
		{
			return;
		}
		_inited = true;
		try
		{
			Type typeFromHandle = typeof(DamageTypes);
			FieldInfo[] fields = typeFromHandle.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			List<FieldInfo> list = new List<FieldInfo>();
			FieldInfo[] array = fields;
			foreach (FieldInfo fieldInfo in array)
			{
				if (fieldInfo.FieldType == typeof(float))
				{
					list.Add(fieldInfo);
				}
			}
			_floatFields = list.ToArray();
			FatalFallPlugin.Dbg($"DamageTypesScaler floatFields={_floatFields.Length}");
		}
		catch
		{
			_floatFields = Array.Empty<FieldInfo>();
		}
	}

	public static void Scale(ref DamageTypes dmg, float factor)
	{
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		if (_floatFields == null || _floatFields.Length == 0)
		{
			return;
		}
		for (int i = 0; i < _floatFields.Length; i++)
		{
			FieldInfo fieldInfo = _floatFields[i];
			float num = (float)fieldInfo.GetValue(dmg);
			if (Math.Abs(num) > 0.0001f)
			{
				fieldInfo.SetValueDirect(__makeref(dmg), num * factor);
			}
		}
	}

	public static float Sum(DamageTypes dmg)
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		float num = 0f;
		if (_floatFields == null || _floatFields.Length == 0)
		{
			return num;
		}
		for (int i = 0; i < _floatFields.Length; i++)
		{
			try
			{
				FieldInfo fieldInfo = _floatFields[i];
				num += (float)fieldInfo.GetValue(dmg);
			}
			catch
			{
			}
		}
		return num;
	}
}
public sealed class SE_PixFatalFall_Bleed : StatusEffect
{
	private float _tick;

	private float _dps;

	private float _minHp;

	public override void Setup(Character character)
	{
		((StatusEffect)this).Setup(character);
		_tick = 0f;
		_dps = FatalFallPlugin.BleedDps();
		_minHp = FatalFallPlugin.BleedMinHp();
	}

	public override void UpdateStatusEffect(float dt)
	{
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b4: Expected O, but got Unknown
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c6: 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_00d2: Unknown result type (might be due to invalid IL or missing references)
		((StatusEffect)this).UpdateStatusEffect(dt);
		if ((Object)(object)base.m_character == (Object)null || _dps <= 0f)
		{
			return;
		}
		_tick += dt;
		if (_tick < 1f)
		{
			return;
		}
		_tick -= 1f;
		try
		{
			float health = base.m_character.GetHealth();
			float num = Mathf.Min(_dps, Mathf.Max(0f, health - _minHp));
			if (!(num <= 0.0001f))
			{
				HitData val = new HitData();
				val.m_point = ((Component)base.m_character).transform.position;
				val.m_dir = Vector3.zero;
				val.m_damage.m_blunt = num;
				base.m_character.Damage(val);
			}
		}
		catch
		{
		}
	}
}
[HarmonyPatch]
internal static class Patches
{
	private struct BorderlineState
	{
		public bool Armed;

		public bool IsFall;
	}

	private static MethodInfo _staggerVec3;

	private static bool _staggerResolved;

	private static readonly Dictionary<int, float> _borderlineNextAllowed = new Dictionary<int, float>(128);

	[HarmonyPatch(typeof(ObjectDB), "Awake")]
	[HarmonyPostfix]
	private static void ObjectDB_Awake_Postfix(ObjectDB __instance)
	{
		EnsureBleedRegistered(__instance, forceIconOverwrite: true);
	}

	[HarmonyPatch(typeof(ObjectDB), "CopyOtherDB")]
	[HarmonyPostfix]
	private static void ObjectDB_CopyOtherDB_Postfix(ObjectDB __instance)
	{
		EnsureBleedRegistered(__instance, forceIconOverwrite: true);
	}

	internal static void EnsureBleedRegistered(ObjectDB odb, bool forceIconOverwrite)
	{
		if ((Object)(object)odb == (Object)null)
		{
			return;
		}
		try
		{
			FatalFallPlugin.EnsureBleedIconLoaded(forceReload: false);
			StatusEffect statusEffect = odb.GetStatusEffect(FatalFallPlugin.BleedSeHash);
			if ((Object)(object)statusEffect != (Object)null)
			{
				statusEffect.m_ttl = FatalFallPlugin.BleedDuration();
				if (forceIconOverwrite && (Object)(object)FatalFallPlugin.BleedIconSprite != (Object)null)
				{
					statusEffect.m_icon = FatalFallPlugin.BleedIconSprite;
				}
				FatalFallPlugin.BleedPrefab = statusEffect;
				return;
			}
			SE_PixFatalFall_Bleed sE_PixFatalFall_Bleed = ScriptableObject.CreateInstance<SE_PixFatalFall_Bleed>();
			((Object)sE_PixFatalFall_Bleed).name = "SE_PixFatalFall_Bleed";
			((StatusEffect)sE_PixFatalFall_Bleed).m_name = "Bleeding";
			((StatusEffect)sE_PixFatalFall_Bleed).m_tooltip = "You are bleeding.";
			((StatusEffect)sE_PixFatalFall_Bleed).m_ttl = FatalFallPlugin.BleedDuration();
			if ((Object)(object)FatalFallPlugin.BleedIconSprite != (Object)null)
			{
				((StatusEffect)sE_PixFatalFall_Bleed).m_icon = FatalFallPlugin.BleedIconSprite;
			}
			odb.m_StatusEffects.Add((StatusEffect)(object)sE_PixFatalFall_Bleed);
			FatalFallPlugin.BleedPrefab = (StatusEffect)(object)sE_PixFatalFall_Bleed;
			FatalFallPlugin.Dbg($"Bleed StatusEffect registered. iconNull={(Object)(object)((StatusEffect)sE_PixFatalFall_Bleed).m_icon == (Object)null} listCount={odb.m_StatusEffects.Count}");
		}
		catch
		{
		}
	}

	private static bool CanBorderlineFire(Character ch)
	{
		if ((Object)(object)ch == (Object)null)
		{
			return false;
		}
		float num = FatalFallPlugin.Now();
		int instanceID = ((Object)ch).GetInstanceID();
		if (_borderlineNextAllowed.TryGetValue(instanceID, out var value) && num < value)
		{
			return false;
		}
		return true;
	}

	private static void ArmBorderlineCooldown(Character ch)
	{
		if (!((Object)(object)ch == (Object)null))
		{
			float num = FatalFallPlugin.BorderlineCooldownSeconds();
			if (!(num <= 0f))
			{
				float num2 = FatalFallPlugin.Now();
				int instanceID = ((Object)ch).GetInstanceID();
				_borderlineNextAllowed[instanceID] = num2 + num;
			}
		}
	}

	[HarmonyPatch(typeof(Character), "RPC_Damage")]
	[HarmonyPrefix]
	private static void Character_RPC_Damage_Prefix(Character __instance, ref HitData hit, ref BorderlineState __state)
	{
		//IL_01f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_0203: Unknown result type (might be due to invalid IL or missing references)
		//IL_0205: 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_00c3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		__state = default(BorderlineState);
		if (!FatalFallPlugin.IsEnabled() || (Object)(object)__instance == (Object)null || hit == null)
		{
			return;
		}
		bool flag = (__state.IsFall = HitIntrospection.IsFallHit(hit));
		if (!flag && !FatalFallPlugin.BorderlineAllDamage())
		{
			return;
		}
		if (FatalFallPlugin.BorderlineSkipIfBubble() && FatalFallPlugin.HasStaffBubble(__instance))
		{
			if (FatalFallPlugin.DebugEnabled())
			{
				FatalFallPlugin.Dbg("Borderline skipped due to Staff bubble active (prevents raw-damage false proc).");
			}
			return;
		}
		if (flag)
		{
			float mul = FatalFallPlugin.GetMul();
			if (Math.Abs(mul - 1f) > 0.0001f)
			{
				try
				{
					DamageTypes dmg = hit.m_damage;
					DamageTypesScaler.Scale(ref dmg, mul);
					hit.m_damage = dmg;
					float fallDamage = HitIntrospection.GetFallDamage(hit);
					if (fallDamage > 0.0001f)
					{
						float value = fallDamage * mul;
						HitIntrospection.SetFallDamage(ref hit, value);
					}
				}
				catch
				{
				}
			}
		}
		if (!FatalFallPlugin.BorderlineEnabled() || !CanBorderlineFire(__instance))
		{
			return;
		}
		float health = __instance.GetHealth();
		float maxHealth = __instance.GetMaxHealth();
		float num = HitIntrospection.SumAllDamage(hit);
		float num2 = num - health;
		if (num2 <= 0f || num2 > FatalFallPlugin.MaxOverkillToSave() || num2 > FatalFallPlugin.BorderlineWindowHp())
		{
			return;
		}
		float num3 = Mathf.Clamp(FatalFallPlugin.LeaveHp(), 0f, maxHealth);
		float num4 = Mathf.Max(0f, health - num3);
		if (num4 <= 0f)
		{
			return;
		}
		float num5 = num4 / num;
		num5 = Mathf.Clamp01(num5);
		if (num5 >= 0.9999f)
		{
			return;
		}
		try
		{
			DamageTypes dmg2 = hit.m_damage;
			DamageTypesScaler.Scale(ref dmg2, num5);
			hit.m_damage = dmg2;
			if (flag)
			{
				float fallDamage2 = HitIntrospection.GetFallDamage(hit);
				if (fallDamage2 > 0.0001f)
				{
					float value2 = fallDamage2 * num5;
					HitIntrospection.SetFallDamage(ref hit, value2);
				}
			}
			__state.Armed = true;
			ArmBorderlineCooldown(__instance);
			if (FatalFallPlugin.DebugEnabled())
			{
				FatalFallPlugin.Dbg($"Borderline armed isFall={flag} hp={health:0.00} incoming={num:0.00} overkill={num2:0.00} capFactor={num5:0.000}");
			}
		}
		catch
		{
			__state = default(BorderlineState);
		}
	}

	[HarmonyPatch(typeof(Character), "RPC_Damage")]
	[HarmonyPostfix]
	private static void Character_RPC_Damage_Postfix(Character __instance, HitData hit, BorderlineState __state)
	{
		if (!__state.Armed || !FatalFallPlugin.IsEnabled() || (Object)(object)__instance == (Object)null)
		{
			return;
		}
		if (FatalFallPlugin.PopupEnabled() && __instance.IsPlayer())
		{
			try
			{
				string[] array = (__state.IsFall ? FatalFallPlugin.PopupChoicesFall() : FatalFallPlugin.PopupChoicesGeneric());
				string text = array[Random.Range(0, array.Length)];
				MessageHud instance = MessageHud.instance;
				if (instance != null)
				{
					instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false);
				}
			}
			catch
			{
			}
		}
		if (__instance.IsPlayer())
		{
			FatalFallPlugin.PlayHeartbeat();
		}
		if (FatalFallPlugin.StaggerEnabled())
		{
			TryStagger(__instance);
		}
		if (FatalFallPlugin.BleedEnabled())
		{
			TryApplyBleedGuaranteedVisible(__instance);
		}
	}

	private static void TryApplyBleedGuaranteedVisible(Character ch)
	{
		try
		{
			if (FatalFallPlugin.BleedSkipIfBubble() && FatalFallPlugin.HasStaffBubble(ch))
			{
				return;
			}
			SEMan sEMan = ch.GetSEMan();
			if (sEMan != null)
			{
				EnsureBleedRegistered(ObjectDB.instance, forceIconOverwrite: true);
				StatusEffect bleedPrefab = FatalFallPlugin.BleedPrefab;
				if (!((Object)(object)bleedPrefab == (Object)null))
				{
					sEMan.RemoveStatusEffect(FatalFallPlugin.BleedSeHash, true);
					sEMan.AddStatusEffect(bleedPrefab, true, 0, 0f);
				}
			}
		}
		catch
		{
		}
	}

	private static void TryStagger(Character ch)
	{
		//IL_009b: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0057: Unknown result type (might be due to invalid IL or missing references)
		//IL_005c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0064: Unknown result type (might be due to invalid IL or missing references)
		//IL_0069: Unknown result type (might be due to invalid IL or missing references)
		//IL_007e: Unknown result type (might be due to invalid IL or missing references)
		//IL_007f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0084: Unknown result type (might be due to invalid IL or missing references)
		try
		{
			if (!_staggerResolved)
			{
				_staggerResolved = true;
				_staggerVec3 = typeof(Character).GetMethod("Stagger", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(Vector3) }, null);
			}
			if (_staggerVec3 == null)
			{
				return;
			}
			Vector3 val = Vector3.back;
			try
			{
				Vector3 forward = ((Component)ch).transform.forward;
				if (((Vector3)(ref forward)).sqrMagnitude > 0.0001f)
				{
					val = -forward;
				}
			}
			catch
			{
			}
			_staggerVec3.Invoke(ch, new object[1] { val * FatalFallPlugin.StaggerStrength() });
		}
		catch
		{
		}
	}
}
internal static class FallTracker
{
	private sealed class State
	{
		public float maxY;

		public bool wasGrounded;

		public float lastFallHitTime;

		public float lastFallVanillaDamage;
	}

	private static readonly Dictionary<int, State> _states = new Dictionary<int, State>(256);

	[ThreadStatic]
	private static bool _applyingExtra;

	private static State Get(Character ch)
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		int instanceID = ((Object)ch).GetInstanceID();
		if (!_states.TryGetValue(instanceID, out var value))
		{
			value = new State();
			value.maxY = ((Component)ch).transform.position.y;
			value.wasGrounded = true;
			value.lastFallHitTime = -999f;
			value.lastFallVanillaDamage = 0f;
			_states[instanceID] = value;
		}
		return value;
	}

	public static void NoteFallHit(Character ch, HitData hit)
	{
		if (!((Object)(object)ch == (Object)null) && hit != null)
		{
			State state = Get(ch);
			state.lastFallHitTime = FatalFallPlugin.Now();
			float num = HitIntrospection.GetFallDamage(hit);
			if (num <= 0.0001f)
			{
				num = HitIntrospection.SumAllDamage(hit);
			}
			state.lastFallVanillaDamage = num;
		}
	}

	public static void OnGroundContactTick(Character ch)
	{
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ac: Expected O, but got Unknown
		//IL_01b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b9: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)ch == (Object)null || !FatalFallPlugin.IsEnabled() || !FatalFallPlugin.UncapEnabled())
		{
			return;
		}
		State state = Get(ch);
		bool flag = false;
		try
		{
			flag = ch.IsOnGround();
		}
		catch
		{
			flag = false;
		}
		float num = 0f;
		try
		{
			num = ((Component)ch).transform.position.y;
		}
		catch
		{
			num = 0f;
		}
		if (!flag)
		{
			if (num > state.maxY)
			{
				state.maxY = num;
			}
			state.wasGrounded = false;
		}
		else
		{
			if (!state.wasGrounded)
			{
				state.wasGrounded = true;
				float num2 = FatalFallPlugin.Now();
				float num3 = FatalFallPlugin.UncapRequireRecentFallHitWindow();
				if (num2 - state.lastFallHitTime > num3)
				{
					state.maxY = num;
					return;
				}
				float num4 = state.maxY - num;
				state.maxY = num;
				if (num4 < 0.5f || num4 > 250f)
				{
					return;
				}
				float num5 = FatalFallPlugin.UncapStartHeight();
				float num6 = FatalFallPlugin.UncapDamagePerMeter();
				if (num6 <= 0f)
				{
					return;
				}
				float num7 = Mathf.Max(0f, num4 - num5) * num6;
				float num8 = FatalFallPlugin.UncapMaxExtraDamage();
				num7 = Mathf.Clamp(num7, 0f, num8);
				if (num7 <= 0.0001f || _applyingExtra)
				{
					return;
				}
				try
				{
					_applyingExtra = true;
					HitData val = new HitData();
					val.m_point = ((Component)ch).transform.position;
					val.m_dir = Vector3.zero;
					val.m_damage.m_blunt = num7;
					ch.Damage(val);
					FatalFallPlugin.Dbg($"Uncap landing: fallHeight={num4:0.0} extra={num7:0.0} recentFallHit={num2 - state.lastFallHitTime:0.000}s");
					return;
				}
				catch
				{
					return;
				}
				finally
				{
					_applyingExtra = false;
				}
			}
			state.maxY = num;
		}
	}
}
[HarmonyPatch]
internal static class FallTrackerPatches
{
	[HarmonyPatch(typeof(Character), "RPC_Damage")]
	[HarmonyPostfix]
	private static void Character_RPC_Damage_Postfix_FallNote(Character __instance, HitData hit)
	{
		if (FatalFallPlugin.IsEnabled() && !((Object)(object)__instance == (Object)null) && hit != null && FatalFallPlugin.UncapEnabled() && HitIntrospection.IsFallHit(hit))
		{
			FallTracker.NoteFallHit(__instance, hit);
		}
	}

	[HarmonyPatch(typeof(Character), "UpdateGroundContact")]
	[HarmonyPostfix]
	private static void Character_UpdateGroundContact_Postfix(Character __instance)
	{
		if (FatalFallPlugin.IsEnabled())
		{
			FallTracker.OnGroundContactTick(__instance);
		}
	}
}