Decompiled source of FallRoll v1.0.2

FallRoll.dll

Decompiled a day ago
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<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)
		{
		}
	}
}