Please disclose if your mod was created primarily 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 FatalFall v0.6.1
FatalFall.dll
Decompiled a month agousing 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.6.1")] public sealed class FatalFallPlugin : BaseUnityPlugin { [CompilerGenerated] private sealed class <ClientEnsureSeRoutine>d__51 : 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__51(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__47 : 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__47(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.6.1"; 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<float> _borderlinePostConfirmMaxHpFraction; private static ConfigEntry<float> _borderlinePostConfirmMaxHpAbsolute; private static ConfigEntry<float> _borderlinePostConfirmMargin; private static ConfigEntry<bool> _borderlineRefundCooldownOnFalseProc; 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_0485: Unknown result type (might be due to invalid IL or missing references) //IL_048f: 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", false, "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. 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."); _borderlineSkipIfBubble = ((BaseUnityPlugin)this).Config.Bind<bool>("Borderline", "SkipIfStaffBubbleActive", true, "If true, borderline will not arm while a Staff of Protection bubble/shield status is active."); _borderlinePostConfirmMaxHpFraction = ((BaseUnityPlugin)this).Config.Bind<float>("Borderline", "PostConfirmMaxHpFraction", 0.35f, "After the hit resolves, effects only trigger if current HP is at or below this fraction of max HP."); _borderlinePostConfirmMaxHpAbsolute = ((BaseUnityPlugin)this).Config.Bind<float>("Borderline", "PostConfirmMaxHpAbsolute", 20f, "After the hit resolves, effects only trigger if current HP is at or below this absolute HP value."); _borderlinePostConfirmMargin = ((BaseUnityPlugin)this).Config.Bind<float>("Borderline", "PostConfirmMargin", 1.5f, "Extra margin added to the post-confirm threshold to reduce missed true saves while still blocking false procs."); _borderlineRefundCooldownOnFalseProc = ((BaseUnityPlugin)this).Config.Bind<bool>("Borderline", "RefundCooldownOnFalseProc", true, "If true, a post-confirmed false proc will refund the cooldown so players are not penalized."); _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."); _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", true, "If true, allows falls to exceed vanilla's effective cap by adding extra landing damage on touchdown."); _uncapStartHeight = ((BaseUnityPlugin)this).Config.Bind<float>("Uncap", "StartHeightMeters", 16f, "Extra damage starts after this many meters of fall height."); _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."); _uncapRequireRecentFallHitWindow = ((BaseUnityPlugin)this).Config.Bind<float>("Uncap", "RequireRecentFallHitSeconds", 0.35f, "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.6.1", "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__47))] private IEnumerator LoadHeartbeatOgg() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <LoadHeartbeatOgg>d__47(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__51))] private IEnumerator ClientEnsureSeRoutine() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <ClientEnsureSeRoutine>d__51(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 float BorderlinePostConfirmMaxHpFraction() { return Mathf.Clamp(_borderlinePostConfirmMaxHpFraction.Value, 0f, 1f); } internal static float BorderlinePostConfirmMaxHpAbsolute() { return Mathf.Clamp(_borderlinePostConfirmMaxHpAbsolute.Value, 0f, 99999f); } internal static float BorderlinePostConfirmMargin() { return Mathf.Clamp(_borderlinePostConfirmMargin.Value, 0f, 99999f); } internal static bool BorderlineRefundCooldownOnFalseProc() { return _borderlineRefundCooldownOnFalseProc != null && _borderlineRefundCooldownOnFalseProc.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; public float DesiredLeave; public float PostConfirmThreshold; } 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; } } } private static void RefundBorderlineCooldown(Character ch) { if (!((Object)(object)ch == (Object)null)) { int instanceID = ((Object)ch).GetInstanceID(); if (_borderlineNextAllowed.ContainsKey(instanceID)) { _borderlineNextAllowed.Remove(instanceID); } } } private static float ComputePostConfirmThreshold(Character ch, float desiredLeave) { if ((Object)(object)ch == (Object)null) { return desiredLeave; } float num = 0f; try { num = ch.GetMaxHealth(); } catch { num = 0f; } float num2 = FatalFallPlugin.BorderlinePostConfirmMaxHpFraction(); float num3 = FatalFallPlugin.BorderlinePostConfirmMaxHpAbsolute(); float num4 = FatalFallPlugin.BorderlinePostConfirmMargin(); float num5 = ((num > 0f) ? (num * num2) : num3); float num6 = Mathf.Min(num3, num5); num6 = Mathf.Max(num6, desiredLeave); num6 += num4; return Mathf.Clamp(num6, 0f, 99999f); } [HarmonyPatch(typeof(Character), "RPC_Damage")] [HarmonyPrefix] private static void Character_RPC_Damage_Prefix(Character __instance, ref HitData hit, ref BorderlineState __state) { //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0228: 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_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: 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()) || (FatalFallPlugin.BorderlineSkipIfBubble() && FatalFallPlugin.HasStaffBubble(__instance))) { 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 num = 0f; float num2 = 0f; try { num = __instance.GetHealth(); } catch { num = 0f; } try { num2 = __instance.GetMaxHealth(); } catch { num2 = 0f; } float num3 = HitIntrospection.SumAllDamage(hit); float num4 = num3 - num; if (num4 <= 0f || num4 > FatalFallPlugin.MaxOverkillToSave() || num4 > FatalFallPlugin.BorderlineWindowHp()) { return; } float num5 = Mathf.Clamp(FatalFallPlugin.LeaveHp(), 0f, (num2 > 0f) ? num2 : 99999f); float num6 = Mathf.Max(0f, num - num5); if (num6 <= 0f) { return; } float num7 = num6 / num3; num7 = Mathf.Clamp01(num7); if (num7 >= 0.9999f) { return; } try { DamageTypes dmg2 = hit.m_damage; DamageTypesScaler.Scale(ref dmg2, num7); hit.m_damage = dmg2; if (flag) { float fallDamage2 = HitIntrospection.GetFallDamage(hit); if (fallDamage2 > 0.0001f) { float value2 = fallDamage2 * num7; HitIntrospection.SetFallDamage(ref hit, value2); } } __state.Armed = true; __state.DesiredLeave = num5; __state.PostConfirmThreshold = ComputePostConfirmThreshold(__instance, num5); ArmBorderlineCooldown(__instance); if (FatalFallPlugin.DebugEnabled()) { FatalFallPlugin.Dbg($"Borderline armed isFall={flag} hp={num:0.00} incoming={num3:0.00} overkill={num4:0.00} capFactor={num7:0.000} postConfirmThreshold={__state.PostConfirmThreshold:0.00}"); } } 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; } float num = 0f; try { num = __instance.GetHealth(); } catch { num = 0f; } if (num > __state.PostConfirmThreshold) { if (FatalFallPlugin.BorderlineRefundCooldownOnFalseProc()) { RefundBorderlineCooldown(__instance); } if (FatalFallPlugin.DebugEnabled()) { FatalFallPlugin.Dbg($"Borderline post-confirm blocked (likely mitigation/absorb/resist). hpAfter={num:0.00} threshold={__state.PostConfirmThreshold:0.00}"); } 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); } } }