using 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 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 Enabled; internal static ConfigEntry MinimumImpactSpeed; internal static ConfigEntry MaximumRollableSpeed; internal static ConfigEntry PerfectReduction; internal static ConfigEntry GoodReduction; internal static ConfigEntry LateReduction; internal static ConfigEntry PerfectWindow; internal static ConfigEntry GoodWindow; internal static ConfigEntry LateWindow; internal static ConfigEntry EnableFeatherFallCheck; internal static ConfigEntry 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("General", "Enable", true, "Enable Fall Roll (timed Jump while falling to soften fall damage)."); MinimumImpactSpeed = ((BaseUnityPlugin)this).Config.Bind("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(1f, 40f), Array.Empty())); MaximumRollableSpeed = ((BaseUnityPlugin)this).Config.Bind("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(5f, 60f), Array.Empty())); PerfectReduction = ((BaseUnityPlugin)this).Config.Bind("Reduction", "Perfect Reduction", 40f, new ConfigDescription("Percent fall damage removed on a Perfect timed Jump.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 90f), Array.Empty())); GoodReduction = ((BaseUnityPlugin)this).Config.Bind("Reduction", "Good Reduction", 25f, new ConfigDescription("Percent fall damage removed on a Good timed Jump.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 90f), Array.Empty())); LateReduction = ((BaseUnityPlugin)this).Config.Bind("Reduction", "Late Reduction", 15f, new ConfigDescription("Percent fall damage removed on a Late timed Jump.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 90f), Array.Empty())); PerfectWindow = ((BaseUnityPlugin)this).Config.Bind("Timing", "Perfect Window", 90f, new ConfigDescription("Max ms between Jump press and impact for Perfect.", (AcceptableValueBase)(object)new AcceptableValueRange(10f, 500f), Array.Empty())); GoodWindow = ((BaseUnityPlugin)this).Config.Bind("Timing", "Good Window", 200f, new ConfigDescription("Max ms between Jump press and impact for Good.", (AcceptableValueBase)(object)new AcceptableValueRange(20f, 800f), Array.Empty())); LateWindow = ((BaseUnityPlugin)this).Config.Bind("Timing", "Late Window", 320f, new ConfigDescription("Max ms between Jump press and impact for Late. Later = no roll.", (AcceptableValueBase)(object)new AcceptableValueRange(50f, 1000f), Array.Empty())); EnableFeatherFallCheck = ((BaseUnityPlugin)this).Config.Bind("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("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) { } } }