Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of FallRoll v1.0.2
FallRoll.dll
Decompiled a day agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("Valheim Overhaul")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Skill-based landing roll with Jump: reduce fall damage on timed press while falling.")] [assembly: AssemblyFileVersion("1.0.2.0")] [assembly: AssemblyInformationalVersion("1.0.2")] [assembly: AssemblyProduct("FallRoll")] [assembly: AssemblyTitle("FallRoll")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.2.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace FallRoll { internal enum RollTier { None, Perfect, Good, Late } internal static class FallRollState { internal static float PeakAirDownSpeed; internal static float JumpPressTime = -999f; internal static bool HasJumpBuffer; internal static float LastBufferLogTime = -999f; internal static void Reset() { PeakAirDownSpeed = 0f; JumpPressTime = -999f; HasJumpBuffer = false; } internal static float ReadDownSpeed(Player player) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return 0f; } if ((Object)(object)((Character)player).m_body != (Object)null) { return Mathf.Max(0f, 0f - ((Character)player).m_body.linearVelocity.y); } return Mathf.Max(0f, 0f - ((Character)player).GetVelocity().y); } internal static void CacheAirVelocity(Player player) { if (!((Object)(object)player == (Object)null)) { float num = ReadDownSpeed(player); if (num > PeakAirDownSpeed) { PeakAirDownSpeed = num; } if (((Character)player).IsOnGround() && num < 1f && !HasJumpBuffer) { PeakAirDownSpeed = 0f; } } } internal static void TryBufferJump(Player player) { if ((Object)(object)player == (Object)null) { return; } float num = ReadDownSpeed(player); float peakAirDownSpeed = PeakAirDownSpeed; if ((!(num < 1f) || !(peakAirDownSpeed < 1f)) && (!HasJumpBuffer || !(Time.time - JumpPressTime < 0.05f))) { JumpPressTime = Time.time; HasJumpBuffer = true; if (Plugin.DebugLog.Value && Time.time - LastBufferLogTime > 0.05f) { LastBufferLogTime = Time.time; Plugin.Log.LogInfo((object)$"Fall Roll Jump buffered | down={num:0.0} peak={peakAirDownSpeed:0.0}"); } } } internal static void ClearJumpBuffer() { HasJumpBuffer = false; JumpPressTime = -999f; } internal static void ExpireJumpBufferIfNeeded() { if (HasJumpBuffer && (Time.time - JumpPressTime) * 1000f > Plugin.LateWindow.Value) { ClearJumpBuffer(); } } internal static void ClearPeak() { PeakAirDownSpeed = 0f; } internal static RollTier GetTier(float deltaMs) { if (deltaMs < 0f) { return RollTier.None; } float value = Plugin.PerfectWindow.Value; float value2 = Plugin.GoodWindow.Value; float value3 = Plugin.LateWindow.Value; if (deltaMs <= value) { return RollTier.Perfect; } if (deltaMs <= value2) { return RollTier.Good; } if (deltaMs <= value3) { return RollTier.Late; } return RollTier.None; } internal static float GetReductionPercent(RollTier tier) { return tier switch { RollTier.Perfect => Plugin.PerfectReduction.Value, RollTier.Good => Plugin.GoodReduction.Value, RollTier.Late => Plugin.LateReduction.Value, _ => 0f, }; } internal static float GetStaminaCost(float impactSpeed) { if (impactSpeed < 14f) { return 15f; } if (impactSpeed < 18f) { return 20f; } if (impactSpeed < 22f) { return 25f; } return 30f; } internal static bool HasFeatherFallProtection(Player player) { if (((player != null) ? ((Character)player).GetSEMan() : null) == null) { return false; } List<StatusEffect> statusEffects = ((Character)player).GetSEMan().GetStatusEffects(); if (statusEffects == null) { return false; } for (int i = 0; i < statusEffects.Count; i++) { StatusEffect obj = statusEffects[i]; SE_Stats val = (SE_Stats)(object)((obj is SE_Stats) ? obj : null); if (val != null) { if (val.m_maxMaxFallSpeed > 0f) { return true; } if (val.m_fallDamageModifier <= -0.5f) { return true; } } } return false; } } [HarmonyPatch(typeof(Character), "Jump")] internal static class CharacterJumpPatch { private static void Prefix(Character __instance) { if (Plugin.Enabled.Value) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && !((Character)val).IsOnGround()) { FallRollState.TryBufferJump(val); } } } } [HarmonyPatch(typeof(Character), "CustomFixedUpdate")] internal static class CharacterCustomFixedUpdatePatch { private static void Prefix(Character __instance) { if (Plugin.Enabled.Value) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer)) { FallRollState.CacheAirVelocity(val); } } } } [HarmonyPatch(typeof(Player), "Update")] internal static class PlayerUpdatePatch { private static void Prefix(Player __instance) { if (Plugin.Enabled.Value && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { if (((Character)__instance).IsOnGround()) { FallRollState.ExpireJumpBufferIfNeeded(); } else if (IsJumpPressedThisFrame()) { FallRollState.TryBufferJump(__instance); } } } private static bool IsJumpPressedThisFrame() { try { if (ZInput.GetButtonDown("Jump")) { return true; } } catch { } try { if (ZInput.GetButtonDown("JoyJump")) { return true; } } catch { } return false; } } [HarmonyPatch(typeof(Character), "RPC_Damage")] internal static class CharacterRpcDamagePatch { private static void Prefix(Character __instance, HitData hit) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 if (Plugin.Enabled.Value && hit != null && (int)hit.m_hitType == 3 && !((Object)(object)__instance == (Object)null) && __instance.IsPlayer()) { Player val = (Player)(object)((__instance is Player) ? __instance : null); if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && (!((Object)(object)((Character)val).m_nview != (Object)null) || ((Character)val).m_nview.IsOwner())) { FallRollApply.TryApplyFallRoll(val, hit); } } } } internal static class FallRollApply { internal static void TryApplyFallRoll(Player player, HitData hit) { float num = FallRollState.PeakAirDownSpeed; if (num < 0.01f) { num = FallRollState.ReadDownSpeed(player); } bool hasJumpBuffer = FallRollState.HasJumpBuffer; float jumpPressTime = FallRollState.JumpPressTime; float num2 = (hasJumpBuffer ? ((Time.time - jumpPressTime) * 1000f) : (-1f)); FallRollState.ClearJumpBuffer(); float num3 = num; FallRollState.ClearPeak(); if (hit.m_damage.m_damage <= 0f) { LogSkip("no damage on hit", num3, hasJumpBuffer, num2); return; } if (Plugin.EnableFeatherFallCheck.Value && FallRollState.HasFeatherFallProtection(player)) { LogSkip("feather/soft-fall effect active", num3, hasJumpBuffer, num2); return; } float value = Plugin.MinimumImpactSpeed.Value; float num4 = Plugin.MaximumRollableSpeed.Value; if (num4 < value) { num4 = value; } if (num3 < value || num3 > num4) { LogSkip($"impact speed {num3:0.0} outside {value}-{num4}", num3, hasJumpBuffer, num2); return; } if (!hasJumpBuffer) { LogSkip("no Jump buffer (press Jump while falling before impact)", num3, hadBuffer: false, num2); return; } RollTier tier = FallRollState.GetTier(num2); if (tier == RollTier.None) { LogSkip($"timing {num2:0} ms outside windows", num3, hadBuffer: true, num2); return; } float staminaCost = FallRollState.GetStaminaCost(num3); if (!((Character)player).HaveStamina(staminaCost)) { LogSkip($"not enough stamina (need {staminaCost:0})", num3, hadBuffer: true, num2); return; } float num5 = FallRollState.GetReductionPercent(tier) / 100f; if (!(num5 <= 0f)) { float damage = hit.m_damage.m_damage; hit.m_damage.m_damage *= 1f - Mathf.Clamp01(num5); ((Character)player).UseStamina(staminaCost); PlayLandingRoll(player); Plugin.Log.LogInfo((object)$"Fall Roll {tier}: {damage:0.0}->{hit.m_damage.m_damage:0.0} dmg | peak={num3:0.0} m/s | {num2:0} ms | stamina {staminaCost:0}"); } } private static void LogSkip(string reason, float speed, bool hadBuffer, float deltaMs) { if (Plugin.DebugLog.Value) { Plugin.Log.LogInfo((object)$"Fall Roll skip: {reason} | peak={speed:0.0} buffer={hadBuffer} deltaMs={deltaMs:0}"); } } private static void PlayLandingRoll(Player player) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return; } if ((Object)(object)((Character)player).m_zanim != (Object)null) { ((Character)player).m_zanim.SetTrigger("dodge"); } Vector3 val = ((Character)player).m_moveDir; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { val = ((Character)player).m_lookDir; val.y = 0f; } if (((Vector3)(ref val)).sqrMagnitude > 0.01f) { ((Vector3)(ref val)).Normalize(); Quaternion rotation = Quaternion.LookRotation(val); ((Component)player).transform.rotation = rotation; if ((Object)(object)((Character)player).m_body != (Object)null) { ((Character)player).m_body.rotation = rotation; } } } } [HarmonyPatch(typeof(Player), "OnDestroy")] internal static class PlayerOnDestroyPatch { private static void Prefix(Player __instance) { if ((Object)(object)__instance == (Object)(object)Player.m_localPlayer) { FallRollState.Reset(); } } } [BepInPlugin("valheimoverhaul.fallroll", "Fall Roll", "1.0.2")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "valheimoverhaul.fallroll"; public const string PluginName = "Fall Roll"; public const string PluginVersion = "1.0.2"; internal static Plugin Instance; internal static ManualLogSource Log; internal static ConfigEntry<bool> Enabled; internal static ConfigEntry<float> MinimumImpactSpeed; internal static ConfigEntry<float> MaximumRollableSpeed; internal static ConfigEntry<float> PerfectReduction; internal static ConfigEntry<float> GoodReduction; internal static ConfigEntry<float> LateReduction; internal static ConfigEntry<float> PerfectWindow; internal static ConfigEntry<float> GoodWindow; internal static ConfigEntry<float> LateWindow; internal static ConfigEntry<bool> EnableFeatherFallCheck; internal static ConfigEntry<bool> DebugLog; private void Awake() { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected O, but got Unknown //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Expected O, but got Unknown //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Expected O, but got Unknown //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Expected O, but got Unknown //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Expected O, but got Unknown //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Expected O, but got Unknown //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0214: Expected O, but got Unknown //IL_025e: Unknown result type (might be due to invalid IL or missing references) Instance = this; Log = ((BaseUnityPlugin)this).Logger; Enabled = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "Enable", true, "Enable Fall Roll (timed Jump while falling to soften fall damage)."); MinimumImpactSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Impact", "Minimum Impact Speed", 6f, new ConfigDescription("Minimum downward impact speed (m/s) required before a Fall Roll can trigger. Vanilla damaging falls often start around ~6–10.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 40f), Array.Empty<object>())); MaximumRollableSpeed = ((BaseUnityPlugin)this).Config.Bind<float>("Impact", "Maximum Rollable Speed", 28f, new ConfigDescription("Above this downward impact speed (m/s), Fall Roll cannot trigger (full vanilla damage).", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), Array.Empty<object>())); PerfectReduction = ((BaseUnityPlugin)this).Config.Bind<float>("Reduction", "Perfect Reduction", 40f, new ConfigDescription("Percent fall damage removed on a Perfect timed Jump.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>())); GoodReduction = ((BaseUnityPlugin)this).Config.Bind<float>("Reduction", "Good Reduction", 25f, new ConfigDescription("Percent fall damage removed on a Good timed Jump.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>())); LateReduction = ((BaseUnityPlugin)this).Config.Bind<float>("Reduction", "Late Reduction", 15f, new ConfigDescription("Percent fall damage removed on a Late timed Jump.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>())); PerfectWindow = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "Perfect Window", 90f, new ConfigDescription("Max ms between Jump press and impact for Perfect.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 500f), Array.Empty<object>())); GoodWindow = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "Good Window", 200f, new ConfigDescription("Max ms between Jump press and impact for Good.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(20f, 800f), Array.Empty<object>())); LateWindow = ((BaseUnityPlugin)this).Config.Bind<float>("Timing", "Late Window", 320f, new ConfigDescription("Max ms between Jump press and impact for Late. Later = no roll.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(50f, 1000f), Array.Empty<object>())); EnableFeatherFallCheck = ((BaseUnityPlugin)this).Config.Bind<bool>("Compatibility", "Enable FeatherFall Check", true, "If true, Fall Roll will not activate while a strong fall-damage / soft-fall status effect is active."); DebugLog = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "Debug Log", false, "Log Fall Roll success/skip reasons to BepInEx log (useful to tune timing/speed)."); new Harmony("valheimoverhaul.fallroll").PatchAll(); Log.LogInfo((object)"Fall Roll 1.0.2 loaded (Jump input)."); } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } }