Decompiled source of Stride v0.6.0

BepInEx\plugins\Stride.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Stride")]
[assembly: AssemblyDescription("Invisible improvements to vanilla movement in Valheim")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Stride")]
[assembly: AssemblyCopyright("Copyright (c) 2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("77f1130e-4e67-4f2d-bd7e-d365e48f9120")]
[assembly: AssemblyFileVersion("0.3.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: AssemblyVersion("0.3.0.0")]
namespace StrideMod;

internal static class PluginInfo
{
	internal const string Guid = "com.b0n3.stride";

	internal const string Name = "Stride";

	internal const string Version = "0.6.0";
}
[BepInPlugin("com.b0n3.stride", "Stride", "0.6.0")]
public class StridePlugin : BaseUnityPlugin
{
	internal enum DebugLogFeature
	{
		CoyoteTime,
		JumpBuffering,
		WallJump,
		FallRoll,
		StaminaDebt
	}

	private sealed class FloatSetting
	{
		private readonly ConfigEntry<string> entry;

		private readonly float defaultValue;

		private readonly float minValue;

		private readonly float maxValue;

		internal float Value => GetFloat(entry, defaultValue, minValue, maxValue);

		internal FloatSetting(ConfigEntry<string> entry, float defaultValue, float minValue, float maxValue)
		{
			this.entry = entry;
			this.defaultValue = defaultValue;
			this.minValue = minValue;
			this.maxValue = maxValue;
		}
	}

	private const string LegacyConfigFileName = "com.b0n3.walljump.cfg";

	private const string CoyoteTimeSection = "Coyote Time";

	private const string JumpBufferingSection = "Jump Buffering";

	private const string WallJumpSection = "Wall Jump";

	private const string FallRollSection = "Fall Roll";

	private const string StaminaDebtSection = "Stamina Debt";

	private const string LoggingSection = "Logging";

	internal static ManualLogSource Log;

	private static readonly Dictionary<string, string> LastInvalidConfigValues = new Dictionary<string, string>();

	private Dictionary<string, string> existingConfigValues = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

	private static FloatSetting coyoteTimeSeconds;

	private static FloatSetting jumpBufferSeconds;

	private static FloatSetting jumpWindowSeconds;

	private static FloatSetting wallMemorySeconds;

	private static FloatSetting wallCheckDistance;

	private static FloatSetting wallCheckRadius;

	private static FloatSetting wallJumpTriggerDistance;

	private static FloatSetting minimumWallJumpEntrySpeed;

	private static FloatSetting fallRollBufferSeconds;

	private static FloatSetting fallRollDamageReduction;

	private static FloatSetting horizontalBounceMinSpeed;

	private static FloatSetting outwardBounceMinSpeed;

	private static FloatSetting reflectionResponsiveness;

	private static FloatSetting inputBiasFactor;

	private static FloatSetting verticalPushForce;

	private static FloatSetting cooldownSeconds;

	internal static ConfigEntry<bool> CoyoteTimeEnabled;

	internal static ConfigEntry<bool> JumpBufferingEnabled;

	internal static ConfigEntry<bool> WallJumpEnabled;

	internal static ConfigEntry<bool> FallRollEnabled;

	internal static ConfigEntry<bool> StaminaDebtEnabled;

	internal static ConfigEntry<bool> RequireForwardPress;

	internal static ConfigEntry<bool> EnableVerboseLogging;

	internal static ConfigEntry<bool> VerboseAllFeatures;

	internal static ConfigEntry<bool> CoyoteTimeVerboseLogging;

	internal static ConfigEntry<bool> JumpBufferVerboseLogging;

	internal static ConfigEntry<bool> WallJumpVerboseLogging;

	internal static ConfigEntry<bool> FallRollVerboseLogging;

	internal static ConfigEntry<bool> StaminaDebtVerboseLogging;

	internal static float CoyoteTimeSeconds => coyoteTimeSeconds.Value;

	internal static float JumpBufferSeconds => jumpBufferSeconds.Value;

	internal static float JumpWindowSeconds => jumpWindowSeconds.Value;

	internal static float WallMemorySeconds => wallMemorySeconds.Value;

	internal static float WallCheckDistance => wallCheckDistance.Value;

	internal static float WallCheckRadius => wallCheckRadius.Value;

	internal static float WallJumpTriggerDistance => wallJumpTriggerDistance.Value;

	internal static float MinimumWallJumpEntrySpeed => minimumWallJumpEntrySpeed.Value;

	internal static float FallRollBufferSeconds => fallRollBufferSeconds.Value;

	internal static float FallRollDamageReduction => fallRollDamageReduction.Value;

	internal static float HorizontalBounceMinSpeed => horizontalBounceMinSpeed.Value;

	internal static float OutwardBounceMinSpeed => outwardBounceMinSpeed.Value;

	internal static float ReflectionResponsiveness => reflectionResponsiveness.Value;

	internal static float InputBiasFactor => inputBiasFactor.Value;

	internal static float VerticalPushForce => verticalPushForce.Value;

	internal static float CooldownSeconds => cooldownSeconds.Value;

	private void Awake()
	{
		//IL_052a: Unknown result type (might be due to invalid IL or missing references)
		Log = ((BaseUnityPlugin)this).Logger;
		EnsureLegacyConfigMigrated();
		existingConfigValues = ReadConfigValues(((BaseUnityPlugin)this).Config.ConfigFilePath);
		CoyoteTimeEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Coyote Time", "Enabled", true, "If this is off, Stride does not allow late jumps after leaving a ledge.");
		coyoteTimeSeconds = BindFloatSetting("Coyote Time", "WindowSeconds", 0.1f, 0f, 1f, "How long you can still jump after running off a ledge.", "General", "CoyoteTimeSeconds");
		JumpBufferingEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Jump Buffering", "Enabled", true, "If this is off, Stride does not save early jump presses before landing.");
		jumpBufferSeconds = BindFloatSetting("Jump Buffering", "WindowSeconds", 0.1f, 0f, 1f, "How early a jump button press is saved before you land.", "General", "JumpBufferSeconds");
		WallJumpEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Wall Jump", "Enabled", true, "If this is off, Stride does not allow wall jumps.");
		jumpWindowSeconds = BindFloatSetting("Wall Jump", "AirJumpWindowSeconds", 0.95f, 0f, 5f, "How long after a normal jump you are still allowed to trigger a wall jump.", "General", "JumpWindowSeconds");
		wallMemorySeconds = BindFloatSetting("Wall Jump", "WallMemorySeconds", 0.05f, 0f, 1f, "How long a wall stays valid after you stop touching it.", "General", "WallMemorySeconds");
		wallCheckDistance = BindFloatSetting("Wall Jump", "WallCheckDistance", 0.575f, 0.05f, 5f, "How far away Stride looks for a wall you can jump from.", "General", "WallCheckDistance");
		wallCheckRadius = BindFloatSetting("Wall Jump", "WallCheckRadius", 0.225f, 0.05f, 3f, "How wide the wall search is around your character.", "General", "WallCheckRadius");
		wallJumpTriggerDistance = BindFloatSetting("Wall Jump", "WallJumpTriggerDistance", 0.18f, 0.01f, 2f, "How close the chosen wall must be before the wall jump actually fires.", "General", "WallJumpTriggerDistance");
		minimumWallJumpEntrySpeed = BindFloatSetting("Wall Jump", "MinimumEntrySpeed", 0.1f, 0f, 20f, "The minimum sideways movement speed that counts as a real wall approach.", "General", "MinimumWallJumpEntrySpeed");
		horizontalBounceMinSpeed = BindFloatSetting("Wall Jump", "MinimumHorizontalBounceSpeed", 9.5f, 0f, 50f, "The minimum sideways speed you keep after bouncing off a wall.", "General", "HorizontalBounceMinSpeed");
		outwardBounceMinSpeed = BindFloatSetting("Wall Jump", "MinimumOutwardBounceSpeed", 6.75f, 0f, 50f, "The minimum speed that pushes you away from the wall.", "General", "OutwardBounceMinSpeed");
		reflectionResponsiveness = BindFloatSetting("Wall Jump", "ReflectionResponsiveness", 1f, 0f, 3f, "How much of your incoming wall approach speed carries into the bounce.", "General", "ReflectionResponsiveness");
		inputBiasFactor = BindFloatSetting("Wall Jump", "InputBiasFactor", 0.12f, 0f, 1f, "How much your movement input can gently steer the wall jump along the wall.", "General", "InputBiasFactor");
		verticalPushForce = BindFloatSetting("Wall Jump", "VerticalPushForce", 8.6f, 0f, 50f, "How much upward lift the wall jump gives you.", "General", "VerticalPushForce");
		cooldownSeconds = BindFloatSetting("Wall Jump", "CooldownSeconds", 0.15f, 0f, 10f, "How long Stride waits before another wall jump can happen.", "General", "CooldownSeconds");
		FallRollEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Fall Roll", "Enabled", true, "If this is off, Stride does not buffer fall rolls or reduce fall damage on landing.");
		fallRollBufferSeconds = BindFloatSetting("Fall Roll", "BufferSeconds", 0.9f, 0f, 2f, "How early you can press dodge before landing and still get a fall roll.", "General", "FallRollBufferSeconds");
		fallRollDamageReduction = BindFloatSetting("Fall Roll", "DamageReduction", 0.5f, 0f, 1f, "How much fall damage a successful fall roll removes. 0.5 means 50% less damage.", "General", "FallRollDamageReduction");
		RequireForwardPress = BindBoolSetting("Wall Jump", "RequireForwardPress", defaultValue: true, "If on, you must be pressing forward to trigger a wall jump.", "General", "RequireForwardPress");
		StaminaDebtEnabled = ((BaseUnityPlugin)this).Config.Bind<bool>("Stamina Debt", "Enabled", true, "If this is off, Stride leaves stamina spending and the stamina bar alone.");
		EnableVerboseLogging = BindBoolSetting("Logging", "EnableVerboseLogging", defaultValue: false, "Main on/off switch for verbose logs. If this is off, Stride prints no verbose logs at all.", "Debug", "VerboseLogging");
		VerboseAllFeatures = BindBoolSetting("Logging", "VerboseAllFeatures", defaultValue: false, "Turns on verbose logs for every feature at once. This only works while EnableVerboseLogging is on.", "Debug", "VerboseLogging");
		CoyoteTimeVerboseLogging = BindBoolSetting("Coyote Time", "VerboseLogging", defaultValue: false, "Extra logs just for coyote time. This only works while Logging.EnableVerboseLogging is on.", "Debug", "VerboseTimingLogging");
		JumpBufferVerboseLogging = BindBoolSetting("Jump Buffering", "VerboseLogging", defaultValue: false, "Extra logs just for jump buffering. This only works while Logging.EnableVerboseLogging is on.", "Debug", "VerboseTimingLogging");
		WallJumpVerboseLogging = BindBoolSetting("Wall Jump", "VerboseLogging", defaultValue: false, "Extra logs for wall detection and wall jumping. This only works while Logging.EnableVerboseLogging is on.", "Debug", "VerboseWallJumpLogging");
		FallRollVerboseLogging = BindBoolSetting("Fall Roll", "VerboseLogging", defaultValue: false, "Extra logs just for fall rolls. This only works while Logging.EnableVerboseLogging is on.", "Debug", "VerboseTimingLogging");
		StaminaDebtVerboseLogging = BindBoolSetting("Stamina Debt", "VerboseLogging", defaultValue: false, "Extra logs just for stamina debt. This only works while Logging.EnableVerboseLogging is on.", "Debug", "VerboseStaminaDebtLogging");
		MigrateLegacyWallDetectionVerboseSetting();
		new Harmony("com.b0n3.stride").PatchAll();
		Log.LogInfo((object)"Stride loaded.");
	}

	private void EnsureLegacyConfigMigrated()
	{
		string configFilePath = ((BaseUnityPlugin)this).Config.ConfigFilePath;
		if (!File.Exists(configFilePath))
		{
			string text = Path.Combine(Paths.ConfigPath, "com.b0n3.walljump.cfg");
			if (File.Exists(text))
			{
				File.Copy(text, configFilePath);
				Log.LogInfo((object)("Migrated config from 'com.b0n3.walljump.cfg' to '" + Path.GetFileName(configFilePath) + "'."));
			}
		}
	}

	private FloatSetting BindFloatSetting(string section, string key, float defaultValue, float minValue, float maxValue, string description, string legacySection, string legacyKey)
	{
		ConfigEntry<string> entry = ((BaseUnityPlugin)this).Config.Bind<string>(section, key, FormatFloat(defaultValue), description + " Range: " + FormatFloat(minValue) + " to " + FormatFloat(maxValue) + ". Invalid values are reset automatically.");
		MigrateEntryValue<string>(entry, legacySection, legacyKey);
		return new FloatSetting(entry, defaultValue, minValue, maxValue);
	}

	private ConfigEntry<bool> BindBoolSetting(string section, string key, bool defaultValue, string description, string legacySection, string legacyKey)
	{
		ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>(section, key, defaultValue, description);
		MigrateEntryValue<bool>(val, legacySection, legacyKey);
		return val;
	}

	private void MigrateEntryValue<T>(ConfigEntry<T> entry, string legacySection, string legacyKey)
	{
		if (entry == null || string.IsNullOrEmpty(legacySection) || string.IsNullOrEmpty(legacyKey))
		{
			return;
		}
		string key = BuildCompositeKey(((ConfigEntryBase)entry).Definition.Section, ((ConfigEntryBase)entry).Definition.Key);
		if (existingConfigValues.ContainsKey(key))
		{
			return;
		}
		string key2 = BuildCompositeKey(legacySection, legacyKey);
		if (!existingConfigValues.TryGetValue(key2, out var value))
		{
			return;
		}
		try
		{
			T val = (T)Convert.ChangeType(value, typeof(T), CultureInfo.InvariantCulture);
			if (!EqualityComparer<T>.Default.Equals(entry.Value, val))
			{
				entry.Value = val;
				((ConfigEntryBase)entry).ConfigFile.Save();
			}
		}
		catch
		{
		}
	}

	private void MigrateLegacyWallDetectionVerboseSetting()
	{
		string key = BuildCompositeKey("Wall Jump", "VerboseLogging");
		if (!existingConfigValues.ContainsKey(key))
		{
			string key2 = BuildCompositeKey("Debug", "VerboseWallDetectionLogging");
			if (existingConfigValues.TryGetValue(key2, out var value) && bool.TryParse(value, out var result) && result && !WallJumpVerboseLogging.Value)
			{
				WallJumpVerboseLogging.Value = true;
				((ConfigEntryBase)WallJumpVerboseLogging).ConfigFile.Save();
			}
		}
	}

	private static Dictionary<string, string> ReadConfigValues(string configFilePath)
	{
		Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
		if (string.IsNullOrEmpty(configFilePath) || !File.Exists(configFilePath))
		{
			return dictionary;
		}
		string section = string.Empty;
		string[] array = File.ReadAllLines(configFilePath);
		for (int i = 0; i < array.Length; i++)
		{
			string text = array[i].Trim();
			if (string.IsNullOrEmpty(text) || text.StartsWith("#"))
			{
				continue;
			}
			if (text.StartsWith("[") && text.EndsWith("]"))
			{
				section = text.Substring(1, text.Length - 2).Trim();
				continue;
			}
			int num = text.IndexOf('=');
			if (num > 0)
			{
				string key = text.Substring(0, num).Trim();
				string value = text.Substring(num + 1).Trim();
				dictionary[BuildCompositeKey(section, key)] = value;
			}
		}
		return dictionary;
	}

	private static string BuildCompositeKey(string section, string key)
	{
		return section + "::" + key;
	}

	private static float GetFloat(ConfigEntry<string> entry, float defaultValue, float minValue, float maxValue)
	{
		string text = entry.Value?.Trim();
		if (string.IsNullOrEmpty(text))
		{
			return WarnAndFallback(entry, text, defaultValue);
		}
		if (!TryParseFloat(text, out var value))
		{
			return WarnAndFallback(entry, text, defaultValue);
		}
		if (value < minValue)
		{
			return WarnAndFallback(entry, text, minValue);
		}
		if (value > maxValue)
		{
			return WarnAndFallback(entry, text, maxValue);
		}
		LastInvalidConfigValues.Remove(((ConfigEntryBase)entry).Definition.Key);
		return value;
	}

	private static float WarnAndFallback(ConfigEntry<string> entry, string raw, float fallbackValue)
	{
		string key = ((ConfigEntryBase)entry).Definition.Key;
		string text = raw ?? "<empty>";
		if (!LastInvalidConfigValues.TryGetValue(key, out var value) || value != text)
		{
			LastInvalidConfigValues[key] = text;
			Log.LogWarning((object)("Config value for " + key + " was invalid ('" + text + "'). Using " + FormatFloat(fallbackValue) + " instead."));
		}
		string text2 = FormatFloat(fallbackValue);
		if (entry.Value != text2)
		{
			entry.Value = text2;
			((ConfigEntryBase)entry).ConfigFile.Save();
		}
		return fallbackValue;
	}

	private static bool TryParseFloat(string raw, out float value)
	{
		if (!float.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out value))
		{
			return float.TryParse(raw, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.CurrentCulture, out value);
		}
		return true;
	}

	private static string FormatFloat(float value)
	{
		return value.ToString("0.###", CultureInfo.InvariantCulture);
	}

	internal static bool IsDebugEnabled(DebugLogFeature feature)
	{
		if (EnableVerboseLogging == null || !EnableVerboseLogging.Value)
		{
			return false;
		}
		if (VerboseAllFeatures != null && VerboseAllFeatures.Value)
		{
			return true;
		}
		switch (feature)
		{
		case DebugLogFeature.CoyoteTime:
			if (CoyoteTimeVerboseLogging != null)
			{
				return CoyoteTimeVerboseLogging.Value;
			}
			return false;
		case DebugLogFeature.JumpBuffering:
			if (JumpBufferVerboseLogging != null)
			{
				return JumpBufferVerboseLogging.Value;
			}
			return false;
		case DebugLogFeature.WallJump:
			if (WallJumpVerboseLogging != null)
			{
				return WallJumpVerboseLogging.Value;
			}
			return false;
		case DebugLogFeature.FallRoll:
			if (FallRollVerboseLogging != null)
			{
				return FallRollVerboseLogging.Value;
			}
			return false;
		case DebugLogFeature.StaminaDebt:
			if (StaminaDebtVerboseLogging != null)
			{
				return StaminaDebtVerboseLogging.Value;
			}
			return false;
		default:
			return false;
		}
	}
}
[HarmonyPatch(typeof(Character), "Jump")]
internal static class CharacterJumpPatch
{
	private static void Prefix(Character __instance, ref bool __state)
	{
		__state = IsTrackedPlayer(__instance) && __instance.IsOnGround();
	}

	private static void Postfix(Character __instance, bool __state)
	{
		if (__state && IsTrackedPlayer(__instance))
		{
			StrideState.RecordGroundJump();
		}
	}

	private static bool IsTrackedPlayer(Character character)
	{
		if ((Object)(object)character != (Object)null)
		{
			return (Object)(object)character == (Object)(object)Player.m_localPlayer;
		}
		return false;
	}
}
[HarmonyPatch(typeof(Player), "Update")]
internal static class PlayerUpdateWallJumpPatch
{
	private struct WallCandidate
	{
		internal Vector3 Normal;

		internal Vector3 Point;

		internal float Distance;

		internal float PlaneDepth;

		internal float BaseScore;

		internal float VelocityFacingScore;

		internal float CastFacingScore;

		internal int PieceRootId;
	}

	private struct WallCluster
	{
		internal Vector3 Normal;

		internal Vector3 AveragePoint;

		internal float PlaneDepth;

		internal float Score;

		internal float BestCandidateScore;

		internal float BestVelocityFacingScore;

		internal float NearestDistance;

		internal float PlayerPlaneOffset;

		internal float ForwardFlowScore;

		internal float FrontFacePenalty;

		internal float Weight;

		internal bool RejectedForDistance;

		internal int HitCount;

		internal int PieceRootId;
	}

	internal struct WallDetectionResult
	{
		internal Vector3 Normal;

		internal float NearestDistance;
	}

	private struct MovementInputSnapshot
	{
		internal Vector2 RawLocalInput;

		internal Vector2 ClampedLocalInput;

		internal Vector3 WorldDirection;

		internal float Magnitude;

		internal float ForwardAmount;

		internal bool HasDirectionalInput
		{
			get
			{
				if (Magnitude >= 0.1f)
				{
					return ((Vector3)(ref WorldDirection)).sqrMagnitude >= 0.01f;
				}
				return false;
			}
		}
	}

	private const float MaxWallNormalY = 0.25f;

	private const float MinimumFacingDot = 0.15f;

	private const float MinimumMoveInputMagnitude = 0.1f;

	private const int WallCastDirectionCount = 8;

	private const int MaxWallHitsPerCast = 32;

	private const float WallClusterNormalDot = 0.92f;

	private const float WallClusterPlaneTolerance = 0.2f;

	private const float WallClusterFrontFaceTolerance = 0.18f;

	private const float WallClusterPointNeighborhood = 1.35f;

	private const float MinimumWallClusterConfidenceLead = 0.2f;

	private const float ForwardFlowBiasWeight = 6f;

	private const float AlternateFlowPreferenceMargin = 8.5f;

	private const float AlternateFlowPreferenceLead = 0.2f;

	private const float AlternateFlowMinimum = 0.45f;

	private const float AlternateFlowMinimumFacing = 0.05f;

	private const float MinimumForwardStickY = 0.35f;

	private const float MinimumVelocityScoreMagnitude = 0.35f;

	private const float SingleHitClusterPenalty = 1.1f;

	private const float StrongSingleHitFacing = 0.9f;

	private const float StrongSingleHitDistance = 0.06f;

	private const float MinimumBounceFloorScale = 0.1f;

	private const float MaximumRememberedApproachBoostRatio = 1.25f;

	private const float MaximumOutwardFloorBoost = 1.5f;

	private const float MaximumHorizontalFloorBoost = 2f;

	internal const float StickyWallNormalDot = 0.8f;

	internal const float StickyWallDistanceTolerance = 0.12f;

	internal const float MinimumApproachVelocityDot = 0.05f;

	internal const float MinimumRememberedWallApproachDot = 0.05f;

	private const float MaximumInputBiasFraction = 0.35f;

	private static readonly FieldRef<Character, Rigidbody> CharacterBodyRef = AccessTools.FieldRefAccess<Character, Rigidbody>("m_body");

	private static readonly FieldRef<Character, SEMan> CharacterSeManRef = AccessTools.FieldRefAccess<Character, SEMan>("m_seman");

	private static readonly FieldRef<Character, float> CharacterSpeedRef = AccessTools.FieldRefAccess<Character, float>("m_speed");

	private static readonly FieldRef<Character, float> LastGroundTouchRef = AccessTools.FieldRefAccess<Character, float>("m_lastGroundTouch");

	private static readonly FieldRef<Character, float> MaxAirAltitudeRef = AccessTools.FieldRefAccess<Character, float>("m_maxAirAltitude");

	private static readonly FieldRef<Character, float> JumpStaminaUsageRef = AccessTools.FieldRefAccess<Character, float>("m_jumpStaminaUsage");

	private static readonly FieldRef<Character, float> JumpForceTiredFactorRef = AccessTools.FieldRefAccess<Character, float>("m_jumpForceTiredFactor");

	private static Player trackedPlayer;

	private static bool lastJumpButtonPressed;

	private static bool wasGroundedLastFrame;

	private static readonly List<WallCandidate> WallCandidateBuffer = new List<WallCandidate>(16);

	private static readonly List<WallCluster> WallClusterBuffer = new List<WallCluster>(16);

	private static readonly Vector3[] WallCastDirectionBuffer = (Vector3[])(object)new Vector3[8];

	private static readonly RaycastHit[] WallHitBuffer = (RaycastHit[])(object)new RaycastHit[32];

	private static void Postfix(Player __instance)
	{
		//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: 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)
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_0104: Unknown result type (might be due to invalid IL or missing references)
		//IL_0109: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer)
		{
			return;
		}
		if ((Object)(object)__instance != (Object)(object)trackedPlayer)
		{
			trackedPlayer = __instance;
			lastJumpButtonPressed = false;
			wasGroundedLastFrame = false;
			StrideState.ResetAll();
			StaminaDebt.ResetTracking();
		}
		bool flag = ((Character)__instance).IsOnGround();
		bool flag2 = ConsumeJumpPressed();
		if (flag || ((Character)__instance).IsSwimming() || ((Character)__instance).IsDead())
		{
			if (flag)
			{
				StrideState.RecordGrounded();
				if (!wasGroundedLastFrame && StrideState.TryConsumeBufferedGroundJump())
				{
					LogDebug(StridePlugin.DebugLogFeature.JumpBuffering, "Jump buffer triggered.");
					((Character)__instance).Jump(false);
				}
			}
			wasGroundedLastFrame = flag;
			StrideState.ResetAirborneState();
			return;
		}
		wasGroundedLastFrame = false;
		Rigidbody val = TryGetBody((Character)(object)__instance);
		Vector3 horizontalVelocity = (((Object)(object)val != (Object)null) ? Vector3.ProjectOnPlane(val.linearVelocity, Vector3.up) : Vector3.zero);
		MovementInputSnapshot movementInput = SampleMovementInput(__instance);
		int num;
		if (StridePlugin.WallJumpEnabled != null)
		{
			num = (StridePlugin.WallJumpEnabled.Value ? 1 : 0);
			if (num != 0)
			{
				goto IL_00e6;
			}
		}
		else
		{
			num = 0;
		}
		StrideState.ClearWallJumpTracking();
		goto IL_00e6;
		IL_00e6:
		WallDetectionResult wallDetection = default(WallDetectionResult);
		bool flag3 = num != 0 && TryFindWall(__instance, flag2, out wallDetection);
		if (flag3)
		{
			StrideState.RecordWallContact(wallDetection.Normal, horizontalVelocity, wallDetection.NearestDistance);
		}
		if (flag2 && StrideState.UsesBufferedJumpTracking())
		{
			StrideState.RecordJumpPressed();
		}
		if (!flag2)
		{
			return;
		}
		if (StrideState.TryConsumeCoyoteJump())
		{
			LastGroundTouchRef.Invoke((Character)(object)__instance) = 0f;
			StrideState.RecordCoyoteJump();
			LogDebug(StridePlugin.DebugLogFeature.CoyoteTime, "Coyote jump triggered.");
			((Character)__instance).Jump(false);
		}
		else
		{
			if (!StrideState.CanWallJump(__instance) || (StridePlugin.RequireForwardPress.Value && !IsPressingForward(movementInput)))
			{
				return;
			}
			if (!StrideState.TryGetWallJumpContact(__instance, flag3, wallDetection, out var wallContact, out var usingRememberedWall))
			{
				LogDebug(StridePlugin.DebugLogFeature.WallJump, "Jump pressed in air, but no valid wall was found.");
				return;
			}
			LogDebug(StridePlugin.DebugLogFeature.WallJump, "Wall jump using " + (usingRememberedWall ? "remembered" : "current") + " wall normal " + FormatVector(wallContact.Normal) + ".");
			if (!(((Vector3)(ref wallContact.Normal)).sqrMagnitude < 0.01f))
			{
				PerformWallJump(__instance, wallContact, movementInput);
			}
		}
	}

	private static bool IsPressingForward(MovementInputSnapshot movementInput)
	{
		return movementInput.ForwardAmount >= 0.35f;
	}

	private static bool ConsumeJumpPressed()
	{
		bool button = ZInput.GetButton("Jump");
		bool result = (button && !lastJumpButtonPressed) || ZInput.GetButtonDown("JoyJump");
		lastJumpButtonPressed = button;
		return result;
	}

	private static bool TryFindWall(Player player, bool logDetails, out WallDetectionResult wallDetection)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_0038: 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_006e: 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_0073: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_0077: Unknown result type (might be due to invalid IL or missing references)
		//IL_009a: Unknown result type (might be due to invalid IL or missing references)
		//IL_009b: Unknown result type (might be due to invalid IL or missing references)
		//IL_009c: 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)
		//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
		//IL_0130: Unknown result type (might be due to invalid IL or missing references)
		//IL_0132: Unknown result type (might be due to invalid IL or missing references)
		wallDetection = default(WallDetectionResult);
		Vector3 centerPoint = ((Character)player).GetCenterPoint();
		Vector3 forward = ((Component)player).transform.forward;
		forward.y = 0f;
		((Vector3)(ref forward)).Normalize();
		Vector3 right = ((Component)player).transform.right;
		right.y = 0f;
		((Vector3)(ref right)).Normalize();
		Rigidbody val = TryGetBody((Character)(object)player);
		Vector3 val2 = (((Object)(object)val != (Object)null) ? Vector3.ProjectOnPlane(val.linearVelocity, Vector3.up) : Vector3.zero);
		Vector3 velocityDirection = val2;
		velocityDirection.y = 0f;
		if (((Vector3)(ref velocityDirection)).sqrMagnitude > 0.01f)
		{
			((Vector3)(ref velocityDirection)).Normalize();
		}
		PopulateWallCastDirections(forward, right, velocityDirection, WallCastDirectionBuffer);
		WallCandidateBuffer.Clear();
		float num = Mathf.Max(0.05f, StridePlugin.WallCheckRadius);
		for (int i = 0; i < WallCastDirectionBuffer.Length; i++)
		{
			Vector3 val3 = WallCastDirectionBuffer[i];
			if (((Vector3)(ref val3)).sqrMagnitude < 0.01f)
			{
				continue;
			}
			int num2 = Physics.SphereCastNonAlloc(centerPoint, num, val3, WallHitBuffer, StridePlugin.WallCheckDistance, -1, (QueryTriggerInteraction)1);
			if (num2 > 0)
			{
				if (num2 == WallHitBuffer.Length && logDetails)
				{
					LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Wall cast hit buffer filled at {WallHitBuffer.Length} hits; some wall candidates may be skipped.");
				}
				AddWallCandidates(player, val3, val2, WallHitBuffer, num2, WallCandidateBuffer, logDetails);
			}
		}
		if (WallCandidateBuffer.Count == 0)
		{
			return false;
		}
		return TryGetSupportedWallNormal(player, WallCandidateBuffer, logDetails, out wallDetection);
	}

	private static void AddWallCandidates(Player player, Vector3 castDirection, Vector3 horizontalVelocity, RaycastHit[] hits, int hitCount, List<WallCandidate> candidates, bool logDetails)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001c: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0030: Unknown result type (might be due to invalid IL or missing references)
		//IL_0031: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0094: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
		//IL_00da: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_0059: Unknown result type (might be due to invalid IL or missing references)
		if (hits == null || hitCount <= 0)
		{
			return;
		}
		Vector3 normalized = ((Vector3)(ref castDirection)).normalized;
		for (int i = 0; i < hitCount; i++)
		{
			if (TryGetWallHitNormal(player, normalized, hits[i], logDetails, out var wallNormal))
			{
				float num = Vector3.Dot(normalized, -wallNormal);
				float num2 = 0f;
				if (((Vector3)(ref horizontalVelocity)).sqrMagnitude >= 0.122499995f)
				{
					num2 = Vector3.Dot(((Vector3)(ref horizontalVelocity)).normalized, -wallNormal);
				}
				float num3 = Mathf.Clamp01(((RaycastHit)(ref hits[i])).distance / Mathf.Max(0.01f, StridePlugin.WallCheckDistance));
				Vector3 point = ((RaycastHit)(ref hits[i])).point;
				Piece componentInParent = ((Component)((RaycastHit)(ref hits[i])).collider).GetComponentInParent<Piece>();
				candidates.Add(new WallCandidate
				{
					Normal = wallNormal,
					Point = point,
					Distance = ((RaycastHit)(ref hits[i])).distance,
					PlaneDepth = Vector3.Dot(wallNormal, point),
					BaseScore = num2 * 1.75f + num * 0.35f - num3 * 0.75f,
					VelocityFacingScore = num2,
					CastFacingScore = num,
					PieceRootId = GetPieceRootId(componentInParent, ((RaycastHit)(ref hits[i])).collider)
				});
			}
		}
	}

	private static void PopulateWallCastDirections(Vector3 fallbackDirection, Vector3 rightDirection, Vector3 velocityDirection, Vector3[] directions)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_0048: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: 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_006e: 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)
		//IL_0089: Unknown result type (might be due to invalid IL or missing references)
		//IL_008a: Unknown result type (might be due to invalid IL or missing references)
		//IL_008f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0096: Unknown result type (might be due to invalid IL or missing references)
		//IL_0097: 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)
		//IL_009f: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		directions[0] = fallbackDirection;
		directions[1] = velocityDirection;
		directions[2] = Quaternion.Euler(0f, 30f, 0f) * fallbackDirection;
		directions[3] = Quaternion.Euler(0f, -30f, 0f) * fallbackDirection;
		directions[4] = Quaternion.Euler(0f, 60f, 0f) * fallbackDirection;
		directions[5] = Quaternion.Euler(0f, -60f, 0f) * fallbackDirection;
		directions[6] = rightDirection;
		directions[7] = -rightDirection;
	}

	private static bool TryGetSupportedWallNormal(Player player, List<WallCandidate> candidates, bool logDetails, out WallDetectionResult wallDetection)
	{
		//IL_0192: Unknown result type (might be due to invalid IL or missing references)
		//IL_0197: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c2: Unknown result type (might be due to invalid IL or missing references)
		wallDetection = default(WallDetectionResult);
		BuildWallClusters(player, candidates, WallClusterBuffer);
		if (WallClusterBuffer.Count == 0)
		{
			return false;
		}
		int num = -1;
		float num2 = float.MinValue;
		float num3 = float.MinValue;
		for (int i = 0; i < WallClusterBuffer.Count; i++)
		{
			WallCluster wallCluster = WallClusterBuffer[i];
			if (wallCluster.FrontFacePenalty > 0f)
			{
				continue;
			}
			float score = wallCluster.Score;
			if (score <= num2)
			{
				if (score > num3)
				{
					num3 = score;
				}
			}
			else
			{
				num3 = num2;
				num2 = score;
				num = i;
			}
		}
		if (num < 0)
		{
			return false;
		}
		WallCluster wallCluster2 = WallClusterBuffer[num];
		int num4 = FindPreferredFlowCluster(WallClusterBuffer, num, num2);
		bool flag = false;
		if (num4 >= 0)
		{
			WallCluster wallCluster3 = WallClusterBuffer[num4];
			if (logDetails)
			{
				LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Switching wall cluster from {num + 1} to {num4 + 1} to preserve along-wall flow. Original flow {wallCluster2.ForwardFlowScore:0.###}, preferred flow {wallCluster3.ForwardFlowScore:0.###}.");
			}
			num = num4;
			num2 = wallCluster3.Score;
			wallCluster2 = wallCluster3;
			flag = true;
		}
		float num5 = num2 - num3;
		if (!flag && WallClusterBuffer.Count > 1 && num5 < 0.2f)
		{
			if (logDetails)
			{
				LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Rejected wall cluster with weak confidence lead {num5:0.###}. Best score {num2:0.###}, runner-up {num3:0.###}.");
				LogWallClusters(WallClusterBuffer);
			}
			return false;
		}
		if (flag && logDetails)
		{
			LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Accepted flow-preserving wall cluster despite confidence lead {num5:0.###}.");
		}
		wallDetection = new WallDetectionResult
		{
			Normal = wallCluster2.Normal,
			NearestDistance = wallCluster2.NearestDistance
		};
		if (logDetails)
		{
			LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Selected wall normal {FormatVector(wallDetection.Normal)} from cluster {num + 1}/{WallClusterBuffer.Count} with {wallCluster2.HitCount} hit(s), nearest {wallCluster2.NearestDistance:0.###}, flow {wallCluster2.ForwardFlowScore:0.###}, score {wallCluster2.Score:0.###}.");
		}
		return true;
	}

	private static int FindPreferredFlowCluster(List<WallCluster> clusters, int bestIndex, float bestScore)
	{
		//IL_00b6: 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)
		//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)
		if (bestIndex < 0 || bestIndex >= clusters.Count)
		{
			return -1;
		}
		WallCluster wallCluster = clusters[bestIndex];
		if (wallCluster.ForwardFlowScore >= 0.45f || wallCluster.BestVelocityFacingScore < 0.75f)
		{
			return -1;
		}
		int result = -1;
		float num = float.MinValue;
		for (int i = 0; i < clusters.Count; i++)
		{
			if (i == bestIndex)
			{
				continue;
			}
			WallCluster wallCluster2 = clusters[i];
			if (wallCluster2.Score < bestScore - 8.5f || wallCluster2.ForwardFlowScore < 0.45f || wallCluster2.ForwardFlowScore < wallCluster.ForwardFlowScore + 0.2f || wallCluster2.BestVelocityFacingScore < 0.05f)
			{
				continue;
			}
			bool num2 = wallCluster2.PieceRootId != 0 && wallCluster2.PieceRootId == wallCluster.PieceRootId;
			Vector3 val = wallCluster2.AveragePoint - wallCluster.AveragePoint;
			bool flag = ((Vector3)(ref val)).sqrMagnitude <= 7.2900004f;
			if (num2 || flag)
			{
				float num3 = wallCluster2.Score + wallCluster2.ForwardFlowScore * 3f + wallCluster2.BestVelocityFacingScore;
				if (!(num3 <= num))
				{
					num = num3;
					result = i;
				}
			}
		}
		return result;
	}

	private static void BuildWallClusters(Player player, List<WallCandidate> candidates, List<WallCluster> clusters)
	{
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0011: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		//IL_014b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0157: Unknown result type (might be due to invalid IL or missing references)
		//IL_015e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0165: Unknown result type (might be due to invalid IL or missing references)
		//IL_016a: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0175: Unknown result type (might be due to invalid IL or missing references)
		//IL_017a: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d2: Unknown result type (might be due to invalid IL or missing references)
		//IL_008e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d0: Unknown result type (might be due to invalid IL or missing references)
		//IL_02d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02e6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0354: Unknown result type (might be due to invalid IL or missing references)
		//IL_035b: Unknown result type (might be due to invalid IL or missing references)
		//IL_036e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0375: Unknown result type (might be due to invalid IL or missing references)
		//IL_037a: Unknown result type (might be due to invalid IL or missing references)
		//IL_037f: Unknown result type (might be due to invalid IL or missing references)
		clusters.Clear();
		Vector3 position = ((Component)player).transform.position;
		Rigidbody val = TryGetBody((Character)(object)player);
		Vector3 horizontalVelocity = (((Object)(object)val != (Object)null) ? Vector3.ProjectOnPlane(val.linearVelocity, Vector3.up) : Vector3.zero);
		for (int i = 0; i < candidates.Count; i++)
		{
			WallCandidate candidate = candidates[i];
			int num = -1;
			for (int j = 0; j < clusters.Count; j++)
			{
				if (CanJoinCluster(candidate, clusters[j]))
				{
					num = j;
					break;
				}
			}
			if (num < 0)
			{
				clusters.Add(new WallCluster
				{
					Normal = candidate.Normal,
					PlaneDepth = candidate.PlaneDepth,
					AveragePoint = candidate.Point,
					Score = candidate.BaseScore,
					BestCandidateScore = candidate.BaseScore,
					BestVelocityFacingScore = candidate.VelocityFacingScore,
					NearestDistance = candidate.Distance,
					Weight = 1f,
					HitCount = 1,
					PieceRootId = candidate.PieceRootId
				});
				continue;
			}
			WallCluster value = clusters[num];
			float num2 = Mathf.Max(0.1f, candidate.BaseScore + 2f);
			float num3 = value.Weight + num2;
			Vector3 val2 = value.Normal * value.Weight + candidate.Normal * num2;
			value.Normal = ((Vector3)(ref val2)).normalized;
			value.PlaneDepth = (value.PlaneDepth * value.Weight + candidate.PlaneDepth * num2) / num3;
			value.AveragePoint = (value.AveragePoint * value.Weight + candidate.Point * num2) / num3;
			value.Weight = num3;
			value.Score += candidate.BaseScore;
			value.HitCount++;
			value.NearestDistance = Mathf.Min(value.NearestDistance, candidate.Distance);
			if (candidate.BaseScore > value.BestCandidateScore)
			{
				value.BestCandidateScore = candidate.BaseScore;
				value.BestVelocityFacingScore = candidate.VelocityFacingScore;
				value.PieceRootId = candidate.PieceRootId;
			}
			clusters[num] = value;
		}
		for (int k = 0; k < clusters.Count; k++)
		{
			WallCluster value2 = clusters[k];
			if (value2.NearestDistance > StridePlugin.WallJumpTriggerDistance)
			{
				value2.RejectedForDistance = true;
				value2.Score = float.MinValue;
				clusters[k] = value2;
			}
			else
			{
				value2.Score += (float)value2.HitCount * 0.45f;
				value2.PlayerPlaneOffset = value2.PlaneDepth - Vector3.Dot(value2.Normal, position);
				value2.ForwardFlowScore = CalculateForwardFlowScore(horizontalVelocity, value2.Normal);
				clusters[k] = value2;
			}
		}
		for (int l = 0; l < clusters.Count; l++)
		{
			WallCluster value3 = clusters[l];
			if (value3.RejectedForDistance)
			{
				continue;
			}
			float num4 = 0f;
			for (int m = 0; m < clusters.Count; m++)
			{
				if (l == m)
				{
					continue;
				}
				WallCluster wallCluster = clusters[m];
				if (!(Vector3.Dot(value3.Normal, wallCluster.Normal) < 0.92f))
				{
					Vector3 val3 = wallCluster.AveragePoint - value3.AveragePoint;
					if (!(((Vector3)(ref val3)).sqrMagnitude > 1.8225001f) && wallCluster.PlayerPlaneOffset > 0f && value3.PlayerPlaneOffset > 0f && wallCluster.PlayerPlaneOffset + 0.18f < value3.PlayerPlaneOffset)
					{
						num4 = Mathf.Max(num4, value3.PlayerPlaneOffset - wallCluster.PlayerPlaneOffset);
					}
				}
			}
			value3.FrontFacePenalty = num4;
			value3.Score -= num4 * 8f;
			value3.Score += Mathf.Clamp01(1f - value3.NearestDistance / Mathf.Max(0.01f, StridePlugin.WallCheckDistance)) * 0.6f;
			value3.Score += (float)Mathf.Min(value3.HitCount, 4) * 0.15f;
			if (value3.HitCount == 1 && (value3.BestVelocityFacingScore < 0.9f || value3.NearestDistance > 0.06f))
			{
				value3.Score -= 1.1f;
			}
			value3.Score += value3.ForwardFlowScore * 6f;
			clusters[l] = value3;
		}
	}

	private static bool CanJoinCluster(WallCandidate candidate, WallCluster cluster)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		if (Vector3.Dot(candidate.Normal, cluster.Normal) < 0.92f)
		{
			return false;
		}
		if (Mathf.Abs(candidate.PlaneDepth - cluster.PlaneDepth) > 0.2f)
		{
			return false;
		}
		return true;
	}

	private static float CalculateForwardFlowScore(Vector3 horizontalVelocity, Vector3 wallNormal)
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_004b: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: 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_005d: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: 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_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_00af: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
		if (((Vector3)(ref horizontalVelocity)).sqrMagnitude < 0.122499995f)
		{
			return 0f;
		}
		Vector3 val = wallNormal;
		val.y = 0f;
		if (((Vector3)(ref val)).sqrMagnitude < 0.01f)
		{
			return 0f;
		}
		((Vector3)(ref val)).Normalize();
		Vector3 normalized = ((Vector3)(ref horizontalVelocity)).normalized;
		float num = Mathf.Max(0f, Vector3.Dot(normalized, -val));
		Vector3 val2 = Vector3.ProjectOnPlane(normalized, val);
		if (((Vector3)(ref val2)).sqrMagnitude < 0.0001f)
		{
			return 0f;
		}
		((Vector3)(ref val2)).Normalize();
		Vector3 val3 = Vector3.Reflect(normalized, val);
		val3.y = 0f;
		if (((Vector3)(ref val3)).sqrMagnitude < 0.0001f)
		{
			return 0f;
		}
		((Vector3)(ref val3)).Normalize();
		float num2 = Vector3.Dot(val3, val2);
		float num3 = Vector3.Dot(val3, normalized);
		return num2 * 0.75f + num3 * 0.25f - num * 0.15f;
	}

	private static bool TryGetWallHitNormal(Player player, Vector3 castDirection, RaycastHit hit, bool logDetails, out Vector3 wallNormal)
	{
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: 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)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_004a: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_005f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0060: Unknown result type (might be due to invalid IL or missing references)
		//IL_0075: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		wallNormal = default(Vector3);
		if ((Object)(object)((RaycastHit)(ref hit)).collider == (Object)null)
		{
			return false;
		}
		Character componentInParent = ((Component)((RaycastHit)(ref hit)).collider).GetComponentInParent<Character>();
		if ((Object)(object)componentInParent != (Object)null && (Object)(object)componentInParent == (Object)(object)player)
		{
			return false;
		}
		Vector3 normal = ((RaycastHit)(ref hit)).normal;
		Vector3 normalized = ((Vector3)(ref normal)).normalized;
		if (Mathf.Abs(normalized.y) > 0.25f)
		{
			return false;
		}
		if (Vector3.Dot(castDirection, -normalized) < 0.15f)
		{
			return false;
		}
		wallNormal = normalized;
		return true;
	}

	private static void PerformWallJump(Player player, StrideState.WallContactSnapshot wallContact, MovementInputSnapshot movementInput)
	{
		//IL_0012: Unknown result type (might be due to invalid IL or missing references)
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_005e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0063: 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_0065: Unknown result type (might be due to invalid IL or missing references)
		//IL_006a: Unknown result type (might be due to invalid IL or missing references)
		//IL_006f: Unknown result type (might be due to invalid IL or missing references)
		//IL_003f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0049: Unknown result type (might be due to invalid IL or missing references)
		//IL_0197: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ad: 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_01bb: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
		//IL_01c4: Unknown result type (might be due to invalid IL or missing references)
		//IL_01cc: Unknown result type (might be due to invalid IL or missing references)
		//IL_01de: Unknown result type (might be due to invalid IL or missing references)
		//IL_021e: Unknown result type (might be due to invalid IL or missing references)
		//IL_022c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0256: Unknown result type (might be due to invalid IL or missing references)
		//IL_0271: Unknown result type (might be due to invalid IL or missing references)
		//IL_0284: Unknown result type (might be due to invalid IL or missing references)
		//IL_0297: Unknown result type (might be due to invalid IL or missing references)
		//IL_0298: Unknown result type (might be due to invalid IL or missing references)
		//IL_0299: Unknown result type (might be due to invalid IL or missing references)
		//IL_029a: Unknown result type (might be due to invalid IL or missing references)
		//IL_02ba: Unknown result type (might be due to invalid IL or missing references)
		//IL_02c6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f8: Unknown result type (might be due to invalid IL or missing references)
		//IL_0105: Unknown result type (might be due to invalid IL or missing references)
		//IL_0107: Unknown result type (might be due to invalid IL or missing references)
		//IL_010e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_0167: Unknown result type (might be due to invalid IL or missing references)
		//IL_0169: Unknown result type (might be due to invalid IL or missing references)
		//IL_0159: Unknown result type (might be due to invalid IL or missing references)
		//IL_0160: Unknown result type (might be due to invalid IL or missing references)
		//IL_0165: Unknown result type (might be due to invalid IL or missing references)
		Rigidbody val = TryGetBody((Character)(object)player);
		if ((Object)(object)val == (Object)null)
		{
			return;
		}
		Vector3 away = wallContact.Normal;
		away.y = 0f;
		((Vector3)(ref away)).Normalize();
		if (((Vector3)(ref away)).sqrMagnitude < 0.01f)
		{
			away = -((Component)player).transform.forward;
			away.y = 0f;
			((Vector3)(ref away)).Normalize();
		}
		Vector3 linearVelocity = val.linearVelocity;
		Vector3 val2 = Vector3.ProjectOnPlane(linearVelocity, Vector3.up);
		bool flag = false;
		if (((Vector3)(ref wallContact.ApproachVelocity)).sqrMagnitude > 0.01f && wallContact.ApproachTime > -100f && Time.time - wallContact.ApproachTime <= StridePlugin.CoyoteTimeSeconds && ((Vector3)(ref wallContact.ApproachVelocity)).magnitude > ((Vector3)(ref val2)).magnitude)
		{
			float num = Mathf.Max(0f, Time.time - wallContact.ApproachTime);
			float num2 = Mathf.Clamp01(1f - num / Mathf.Max(0.01f, StridePlugin.CoyoteTimeSeconds));
			num2 *= num2;
			Vector3 val3 = Vector3.Lerp(val2, wallContact.ApproachVelocity, num2);
			float magnitude = ((Vector3)(ref val2)).magnitude;
			float num3 = ((magnitude > 0.01f) ? (magnitude * 1.25f) : ((Vector3)(ref wallContact.ApproachVelocity)).magnitude);
			float magnitude2 = ((Vector3)(ref val3)).magnitude;
			if (magnitude2 > num3 && magnitude2 > 0.01f)
			{
				val3 = ((Vector3)(ref val3)).normalized * num3;
			}
			val2 = val3;
			flag = true;
		}
		float magnitude3 = ((Vector3)(ref val2)).magnitude;
		if (magnitude3 < StridePlugin.MinimumWallJumpEntrySpeed)
		{
			LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Allowing wall jump despite low incoming horizontal speed {magnitude3:0.###} because wall contact is valid.");
		}
		bool isTired = IsVanillaTiredJump(player, linearVelocity);
		Vector3 currentVelocity = default(Vector3);
		((Vector3)(ref currentVelocity))..ctor(val2.x, linearVelocity.y, val2.z);
		Vector3 val4 = BuildWallJumpVelocity(player, currentVelocity, away, isTired, movementInput);
		LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Wall jump incoming velocity {FormatVector(linearVelocity)} speed {((Vector3)(ref linearVelocity)).magnitude:0.###} direction {FormatDirection(linearVelocity)}.");
		if (flag)
		{
			LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Wall jump used decayed stored wall approach horizontal {FormatVector(val2)} speed {magnitude3:0.###}.");
		}
		LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Wall jump launch input local {FormatVector2(movementInput.RawLocalInput)} clamped {FormatVector2(movementInput.ClampedLocalInput)} magnitude {movementInput.Magnitude:0.###} forward {movementInput.ForwardAmount:0.###} world {FormatVector(movementInput.WorldDirection)}.");
		LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Wall jump outgoing velocity {FormatVector(val4)} speed {((Vector3)(ref val4)).magnitude:0.###} direction {FormatDirection(val4)}.");
		LogDebug(StridePlugin.DebugLogFeature.WallJump, BuildWallJumpLaunchReport(wallContact, away, linearVelocity, val2, val4, movementInput, flag));
		MaxAirAltitudeRef.Invoke((Character)(object)player) = ((Component)player).transform.position.y;
		((Character)player).ForceJump(val4, true);
		StrideState.RecordWallJump();
		LogDebug(StridePlugin.DebugLogFeature.WallJump, "Wall jump triggered.");
	}

	private static bool IsVanillaTiredJump(Player player, Vector3 currentVelocity)
	{
		//IL_0052: Unknown result type (might be due to invalid IL or missing references)
		float num = JumpStaminaUsageRef.Invoke((Character)(object)player);
		bool num2 = !((Character)player).HaveStamina(num);
		if (num2 && ((Character)player).IsPlayer())
		{
			Hud instance = Hud.instance;
			if (instance != null)
			{
				instance.StaminaBarEmptyFlash();
			}
		}
		float num3 = CharacterSpeedRef.Invoke((Character)(object)player);
		SEMan val = CharacterSeManRef.Invoke((Character)(object)player);
		if (val != null)
		{
			val.ApplyStatusEffectSpeedMods(ref num3, currentVelocity);
		}
		if (!num2)
		{
			return num3 <= 0f;
		}
		return true;
	}

	private static Vector3 BuildWallJumpVelocity(Player player, Vector3 currentVelocity, Vector3 away, bool isTired, MovementInputSnapshot movementInput)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_0015: Unknown result type (might be due to invalid IL or missing references)
		//IL_0036: Unknown result type (might be due to invalid IL or missing references)
		//IL_0037: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: 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_004e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0053: Unknown result type (might be due to invalid IL or missing references)
		//IL_0058: Unknown result type (might be due to invalid IL or missing references)
		//IL_016c: Unknown result type (might be due to invalid IL or missing references)
		//IL_016e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_0076: Unknown result type (might be due to invalid IL or missing references)
		//IL_0078: Unknown result type (might be due to invalid IL or missing references)
		//IL_0079: 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_0080: Unknown result type (might be due to invalid IL or missing references)
		//IL_0081: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_017c: Unknown result type (might be due to invalid IL or missing references)
		//IL_017e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0184: Unknown result type (might be due to invalid IL or missing references)
		//IL_0189: Unknown result type (might be due to invalid IL or missing references)
		//IL_018e: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bd: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
		//IL_01de: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e8: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0101: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fb: Unknown result type (might be due to invalid IL or missing references)
		//IL_0200: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_01af: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
		//IL_021d: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01bb: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = Vector3.ProjectOnPlane(currentVelocity, Vector3.up);
		float magnitude = ((Vector3)(ref val)).magnitude;
		float num = Vector3.Dot(val, away);
		float num2 = Mathf.Max(0f, 0f - num);
		float num3 = Mathf.Max(0f, num);
		Vector3 val2 = val - away * num + away * (num3 + num2 * StridePlugin.ReflectionResponsiveness);
		if (movementInput.HasDirectionalInput)
		{
			Vector3 worldDirection = movementInput.WorldDirection;
			float magnitude2 = movementInput.Magnitude;
			Vector3 val3 = Vector3.ProjectOnPlane(val2, away);
			Vector3 val4 = Vector3.ProjectOnPlane(worldDirection, away);
			if (((Vector3)(ref val4)).sqrMagnitude > 0.01f && (((Vector3)(ref val3)).sqrMagnitude < 0.01f || Vector3.Dot(((Vector3)(ref val3)).normalized, ((Vector3)(ref val4)).normalized) >= -0.1f))
			{
				((Vector3)(ref val4)).Normalize();
				float num4 = Mathf.Max(0.1f, Mathf.Min(StridePlugin.HorizontalBounceMinSpeed * StridePlugin.InputBiasFactor, ((Vector3)(ref val2)).magnitude * 0.35f));
				val2 += val4 * (num4 * magnitude2);
			}
		}
		float num5 = Mathf.Lerp(0.1f, 1f, Mathf.Clamp01(magnitude / Mathf.Max(0.01f, StridePlugin.HorizontalBounceMinSpeed)));
		float num6 = Mathf.Max(num2, StridePlugin.OutwardBounceMinSpeed * num5);
		float num7 = Mathf.Max(magnitude, StridePlugin.HorizontalBounceMinSpeed * num5);
		float num8 = Mathf.Min(num6, Mathf.Max(num2, magnitude + 1.5f));
		float num9 = Mathf.Min(num7, magnitude + 2f);
		float num10 = Vector3.Dot(val2, away);
		if (num10 < num8)
		{
			val2 += away * (num8 - num10);
		}
		float magnitude3 = ((Vector3)(ref val2)).magnitude;
		if (magnitude3 < num9)
		{
			val2 = ((magnitude3 > 0.01f) ? (val2 / magnitude3) : away) * num9;
		}
		float num11 = Mathf.Max(currentVelocity.y * 0.25f, StridePlugin.VerticalPushForce);
		Vector3 val5 = val2 + Vector3.up * num11;
		if (isTired)
		{
			val5 *= JumpForceTiredFactorRef.Invoke((Character)(object)player);
		}
		SEMan val6 = CharacterSeManRef.Invoke((Character)(object)player);
		if (val6 != null)
		{
			val6.ApplyStatusEffectJumpMods(ref val5);
		}
		return val5;
	}

	private static MovementInputSnapshot SampleMovementInput(Player player)
	{
		//IL_0008: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00ed: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f2: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f5: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
		//IL_011b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0120: Unknown result type (might be due to invalid IL or missing references)
		//IL_015a: Unknown result type (might be due to invalid IL or missing references)
		//IL_015f: Unknown result type (might be due to invalid IL or missing references)
		//IL_0141: Unknown result type (might be due to invalid IL or missing references)
		//IL_0146: Unknown result type (might be due to invalid IL or missing references)
		//IL_0199: Unknown result type (might be due to invalid IL or missing references)
		//IL_019a: Unknown result type (might be due to invalid IL or missing references)
		//IL_01a0: 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_01a6: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b1: Unknown result type (might be due to invalid IL or missing references)
		//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
		//IL_0180: Unknown result type (might be due to invalid IL or missing references)
		//IL_0185: Unknown result type (might be due to invalid IL or missing references)
		//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_0201: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
		//IL_01e5: Unknown result type (might be due to invalid IL or missing references)
		//IL_01ec: Unknown result type (might be due to invalid IL or missing references)
		//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
		MovementInputSnapshot result = default(MovementInputSnapshot);
		Vector2 val = Vector2.zero;
		if (ZInput.GetButton("Forward"))
		{
			val.y += 1f;
		}
		if (ZInput.GetButton("Backward"))
		{
			val.y -= 1f;
		}
		if (ZInput.GetButton("Right"))
		{
			val.x += 1f;
		}
		if (ZInput.GetButton("Left"))
		{
			val.x -= 1f;
		}
		val.x += ZInput.GetJoyLeftStickX(true);
		val.y += 0f - ZInput.GetJoyLeftStickY(true);
		result.RawLocalInput = val;
		result.ForwardAmount = Mathf.Clamp01(Mathf.Max(val.y, 0f));
		result.Magnitude = Mathf.Clamp01(((Vector2)(ref val)).magnitude);
		if (result.Magnitude < 0.1f)
		{
			return result;
		}
		val = (result.ClampedLocalInput = Vector2.ClampMagnitude(val, 1f));
		Transform obj = (((Object)(object)GameCamera.instance != (Object)null) ? ((Component)GameCamera.instance).transform : ((Component)player).transform);
		Vector3 forward = obj.forward;
		forward.y = 0f;
		if (((Vector3)(ref forward)).sqrMagnitude < 0.01f)
		{
			forward = ((Component)player).transform.forward;
			forward.y = 0f;
		}
		((Vector3)(ref forward)).Normalize();
		Vector3 right = obj.right;
		right.y = 0f;
		if (((Vector3)(ref right)).sqrMagnitude < 0.01f)
		{
			right = ((Component)player).transform.right;
			right.y = 0f;
		}
		((Vector3)(ref right)).Normalize();
		Vector3 val2 = forward * val.y + right * val.x;
		if (((Vector3)(ref val2)).sqrMagnitude < 0.01f)
		{
			result.Magnitude = 0f;
			result.ForwardAmount = 0f;
			result.ClampedLocalInput = Vector2.zero;
			result.WorldDirection = Vector3.zero;
			return result;
		}
		result.WorldDirection = ((Vector3)(ref val2)).normalized;
		return result;
	}

	private static void LogDebug(StridePlugin.DebugLogFeature feature, string message)
	{
		if (StridePlugin.IsDebugEnabled(feature))
		{
			StridePlugin.Log.LogInfo((object)message);
		}
	}

	private static void LogWallClusters(List<WallCluster> clusters)
	{
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		for (int i = 0; i < clusters.Count; i++)
		{
			WallCluster wallCluster = clusters[i];
			LogDebug(StridePlugin.DebugLogFeature.WallJump, $"Cluster {i + 1}: normal {FormatVector(wallCluster.Normal)}, hits {wallCluster.HitCount}, plane offset {wallCluster.PlayerPlaneOffset:0.###}, nearest {wallCluster.NearestDistance:0.###}, score {wallCluster.Score:0.###}, best facing {wallCluster.BestVelocityFacingScore:0.###}, flow {wallCluster.ForwardFlowScore:0.###}, penalty {wallCluster.FrontFacePenalty:0.###}, rejectedDistance {wallCluster.RejectedForDistance}, piece {wallCluster.PieceRootId}.");
		}
	}

	private static int GetPieceRootId(Piece piece, Collider collider)
	{
		if ((Object)(object)piece != (Object)null)
		{
			return ((Object)((Component)piece).gameObject).GetInstanceID();
		}
		if ((Object)(object)collider != (Object)null)
		{
			return ((Object)((Component)((Component)collider).transform.root).gameObject).GetInstanceID();
		}
		return 0;
	}

	internal static string FormatVector(Vector3 value)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		return $"({value.x:0.###}, {value.y:0.###}, {value.z:0.###})";
	}

	internal static string FormatVector2(Vector2 value)
	{
		//IL_0005: Unknown result type (might be due to invalid IL or missing references)
		//IL_0010: Unknown result type (might be due to invalid IL or missing references)
		return $"({value.x:0.###}, {value.y:0.###})";
	}

	private static string BuildWallJumpLaunchReport(StrideState.WallContactSnapshot wallContact, Vector3 away, Vector3 currentVelocity, Vector3 launchHorizontalVelocity, Vector3 jumpVelocity, MovementInputSnapshot movementInput, bool usedSnapshotApproachVelocity)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0018: Unknown result type (might be due to invalid IL or missing references)
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Unknown result type (might be due to invalid IL or missing references)
		//IL_0029: Unknown result type (might be due to invalid IL or missing references)
		//IL_002a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0032: Unknown result type (might be due to invalid IL or missing references)
		//IL_0033: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: Unknown result type (might be due to invalid IL or missing references)
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0044: Unknown result type (might be due to invalid IL or missing references)
		//IL_0045: Unknown result type (might be due to invalid IL or missing references)
		//IL_004d: Unknown result type (might be due to invalid IL or missing references)
		//IL_0068: Unknown result type (might be due to invalid IL or missing references)
		//IL_006d: 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)
		//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e7: Unknown result type (might be due to invalid IL or missing references)
		//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
		//IL_0113: Unknown result type (might be due to invalid IL or missing references)
		//IL_0146: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = Vector3.ProjectOnPlane(currentVelocity, Vector3.up);
		Vector3 val2 = Vector3.ProjectOnPlane(jumpVelocity, Vector3.up);
		float num = Vector3.Dot(val, away);
		float num2 = Vector3.Dot(launchHorizontalVelocity, away);
		float num3 = Vector3.Dot(val2, away);
		float num4 = CalculateTangentialSpeed(val, away);
		float num5 = CalculateTangentialSpeed(launchHorizontalVelocity, away);
		float num6 = CalculateTangentialSpeed(val2, away);
		float y = jumpVelocity.y;
		float num7 = (movementInput.HasDirectionalInput ? Vector3.Dot(movementInput.WorldDirection, away) : 0f);
		return string.Format("Wall jump report | wall {0} near {1:0.###} | input raw {2} clamp {3} mag {4:0.###} fwd {5:0.###} align {6:0.###} world {7} | inH {8} outDot {9:0.###} tan {10:0.###} | launchH {11} outDot {12:0.###} tan {13:0.###} mem {14} | outH {15} outDot {16:0.###} tan {17:0.###} up {18:0.###}", FormatVector(wallContact.Normal), wallContact.NearestDistance, FormatVector2(movementInput.RawLocalInput), FormatVector2(movementInput.ClampedLocalInput), movementInput.Magnitude, movementInput.ForwardAmount, num7, FormatVector(movementInput.WorldDirection), FormatVector(val), num, num4, FormatVector(launchHorizontalVelocity), num2, num5, usedSnapshotApproachVelocity ? "yes" : "no", FormatVector(val2), num3, num6, y);
	}

	private static float CalculateTangentialSpeed(Vector3 velocity, Vector3 away)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Unknown result type (might be due to invalid IL or missing references)
		//IL_0003: Unknown result type (might be due to invalid IL or missing references)
		//IL_0009: Unknown result type (might be due to invalid IL or missing references)
		//IL_000e: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		Vector3 val = velocity - away * Vector3.Dot(velocity, away);
		return ((Vector3)(ref val)).magnitude;
	}

	private static string FormatDirection(Vector3 value)
	{
		//IL_0016: Unknown result type (might be due to invalid IL or missing references)
		if (((Vector3)(ref value)).sqrMagnitude < 0.0001f)
		{
			return "(0, 0, 0)";
		}
		return FormatVector(((Vector3)(ref value)).normalized);
	}

	internal static Rigidbody TryGetBody(Character character)
	{
		if (!((Object)(object)character == (Object)null))
		{
			return CharacterBodyRef.Invoke(character);
		}
		return null;
	}
}
[HarmonyPatch(typeof(Player), "SetControls")]
internal static class PlayerSetControlsFallRollPatch
{
	private static readonly FieldRef<Player, bool> CrouchToggledRef = AccessTools.FieldRefAccess<Player, bool>("m_crouchToggled");

	private static Player trackedPlayer;

	private static bool lastDodgeIntent;

	private static void Prefix(Player __instance, bool blockHold, bool jump, bool dodge)
	{
		//IL_0079: Unknown result type (might be due to invalid IL or missing references)
		//IL_0093: Unknown result type (might be due to invalid IL or missing references)
		//IL_0100: Unknown result type (might be due to invalid IL or missing references)
		//IL_0117: Unknown result type (might be due to invalid IL or missing references)
		if ((Object)(object)__instance != (Object)(object)trackedPlayer)
		{
			trackedPlayer = __instance;
			lastDodgeIntent = false;
		}
		if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || ((Character)__instance).IsDead() || ((Character)__instance).IsSwimming())
		{
			lastDodgeIntent = false;
			return;
		}
		if (StridePlugin.FallRollEnabled == null || !StridePlugin.FallRollEnabled.Value)
		{
			lastDodgeIntent = false;
			return;
		}
		Rigidbody val = PlayerUpdateWallJumpPatch.TryGetBody((Character)(object)__instance);
		if ((Object)(object)val == (Object)null || ((Character)__instance).IsOnGround() || val.linearVelocity.y >= -0.1f)
		{
			lastDodgeIntent = false;
			return;
		}
		bool flag = false;
		if ((int)ZInput.InputLayout == 0 || !ZInput.IsGamepadActive())
		{
			bool flag2 = ((Character)__instance).IsCrouching() || CrouchToggledRef.Invoke(__instance);
			flag = jump && (blockHold || flag2);
		}
		else
		{
			bool flag3 = ((Character)__instance).IsCrouching() || CrouchToggledRef.Invoke(__instance);
			flag = dodge && (blockHold || flag3);
		}
		if (!flag)
		{
			lastDodgeIntent = false;
		}
		else if (!lastDodgeIntent)
		{
			lastDodgeIntent = true;
			StrideState.RecordFallRollAttempt(0f - val.linearVelocity.y);
			LogDebug(StridePlugin.DebugLogFeature.FallRoll, $"Buffered fall roll attempt at downward speed {0f - val.linearVelocity.y:0.###}.");
		}
	}

	private static void LogDebug(StridePlugin.DebugLogFeature feature, string message)
	{
		if (StridePlugin.IsDebugEnabled(feature))
		{
			StridePlugin.Log.LogInfo((object)message);
		}
	}
}
[HarmonyPatch(typeof(SEMan), "ModifyFallDamage")]
internal static class SEManModifyFallDamagePatch
{
	private static readonly FieldRef<SEMan, Character> OwnerRef = AccessTools.FieldRefAccess<SEMan, Character>("m_character");

	private static readonly FieldRef<Player, EffectList> PerfectDodgeEffectsRef = AccessTools.FieldRefAccess<Player, EffectList>("m_perfectDodgeEffects");

	private static void Prefix(SEMan __instance, float baseDamage, ref float damage)
	{
		if (StridePlugin.FallRollEnabled != null && StridePlugin.FallRollEnabled.Value)
		{
			Character val = ((__instance != null) ? OwnerRef.Invoke(__instance) : null);
			if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer))
			{
				LogFallRollResult($"HOOK: baseDamage={baseDamage:0.###} damageBeforeEffects={damage:0.###} hasBufferedAttempt={StrideState.HasPendingFallRollAttemptForDebug()}");
			}
		}
	}

	private static void Postfix(SEMan __instance, float baseDamage, ref float damage)
	{
		//IL_009d: Unknown result type (might be due to invalid IL or missing references)
		//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
		if (StridePlugin.FallRollEnabled == null || !StridePlugin.FallRollEnabled.Value || __instance == null)
		{
			return;
		}
		Character val = OwnerRef.Invoke(__instance);
		if (!((Object)(object)val == (Object)null) && !((Object)(object)val != (Object)(object)Player.m_localPlayer) && !(damage <= 0f) && StrideState.TryConsumeFallRollDamageReduction(out var reduction, out var bufferedAge, out var downwardSpeed))
		{
			float num = Mathf.Clamp01(reduction);
			float num2 = damage;
			damage *= 1f - num;
			float num3 = Mathf.Max(0f, num2 - damage);
			Player val2 = (Player)(object)((val is Player) ? val : null);
			if (val2 != null)
			{
				PerfectDodgeEffectsRef.Invoke(val2).Create(((Component)val2).transform.position, Quaternion.identity, ((Component)val2).transform, 1f, -1);
				((Character)val2).Message((MessageType)2, "Perfect landing", 0, (Sprite)null);
			}
			LogFallRollResult($"SUCCESS: bufferedAge={bufferedAge:0.###}s downwardSpeed={downwardSpeed:0.###} baseDamage={baseDamage:0.###} originalDamage={num2:0.###} savedDamage={num3:0.###} finalDamage={damage:0.###} reduction={num * 100f:0.#}%");
			LogDebug(StridePlugin.DebugLogFeature.FallRoll, $"Applied buffered fall roll mitigation. Reduced fall damage by {num * 100f:0.#}% after {bufferedAge:0.###} s buffer at downward speed {downwardSpeed:0.###}.");
		}
	}

	private static void LogDebug(StridePlugin.DebugLogFeature feature, string message)
	{
		if (StridePlugin.IsDebugEnabled(feature))
		{
			StridePlugin.Log.LogInfo((object)message);
		}
	}

	private static void LogFallRollResult(string message)
	{
		if (StridePlugin.IsDebugEnabled(StridePlugin.DebugLogFeature.FallRoll))
		{
			ManualLogSource log = StridePlugin.Log;
			if (log != null)
			{
				log.LogInfo((object)("[FallRoll] " + message));
			}
		}
	}
}
[HarmonyPatch(typeof(Player), "HaveStamina")]
internal static class PlayerHaveStaminaDebtPatch
{
	private static bool Prefix(Player __instance, float amount, ref bool __result)
	{
		if (!StaminaDebt.ShouldManage(__instance))
		{
			return true;
		}
		if (StaminaDebt.IsInDebt(__instance))
		{
			StaminaDebt.LogBlockedSpend(__instance, amount);
			__result = false;
			return false;
		}
		if (StaminaDebt.CanBorrowForChunkedUse(__instance, amount))
		{
			__result = true;
			return false;
		}
		return true;
	}
}
[HarmonyPatch(typeof(Player), "RPC_UseStamina")]
internal static class PlayerRpcUseStaminaDebtPatch
{
	private static bool Prefix(Player __instance, float v)
	{
		if (!StaminaDebt.ShouldManage(__instance))
		{
			return true;
		}
		if (!StaminaDebt.IsFinite(v) || v == 0f)
		{
			return false;
		}
		if (StaminaDebt.IsInDebt(__instance))
		{
			StaminaDebt.LogBlockedSpend(__instance, v);
			return false;
		}
		if (!StaminaDebt.CanBorrowForChunkedUse(__instance, v))
		{
			return true;
		}
		StaminaDebt.ApplyDebt(__instance, v);
		return false;
	}
}
[HarmonyPatch(typeof(Player), "UpdateStats", new Type[] { typeof(float) })]
internal static class PlayerUpdateStatsDebtPatch
{
	private static void Prefix(Player __instance, out float __state)
	{
		__state = StaminaDebt.GetCurrentStamina(__instance);
		if (StaminaDebt.ShouldManage(__instance) && StaminaDebt.IsInDebt(__instance))
		{
			StaminaDebt.ClearRegenDelay(__instance);
		}
	}

	private static void Postfix(Player __instance, float __state)
	{
		StaminaDebt.LogUpdate(__instance, __state);
	}
}
[HarmonyPatch(typeof(Hud), "UpdateStamina")]
internal static class HudStaminaDebtPatch
{
	private static void Postfix(Hud __instance, Player player)
	{
		if (!((Object)(object)__instance == (Object)null) && !((Object)(object)player == (Object)null) && !((Object)(object)player != (Object)(object)Player.m_localPlayer))
		{
			if (StaminaDebt.IsInDebt(player))
			{
				StaminaDebt.ApplyHudDebtVisuals(__instance, player);
			}
			else
			{
				StaminaDebt.RestoreHudVisuals(__instance);
			}
		}
	}
}
[HarmonyPatch(typeof(Player), "UpdateFood", new Type[]
{
	typeof(float),
	typeof(bool)
})]
internal static class PlayerUpdateFoodDebtPatch
{
	private static void Prefix(Player __instance, out float __state)
	{
		__state = (StaminaDebt.ShouldManage(__instance) ? StaminaDebt.GetCurrentStamina(__instance) : 0f);
	}

	private static void Postfix(Player __instance, float __state)
	{
		if (StaminaDebt.ShouldManage(__instance) && !(__state >= 0f))
		{
			StaminaDebt.RestoreDebtAfterFoodUpdateClamp(__instance, __state);
		}
	}
}
internal static class StaminaDebt
{
	private const float MinimumChunkedDebtUse = 5f;

	private const float MaxDebtVisualRatio = 1.25f;

	private static readonly Color DebtBarColor = new Color(1f, 0.2f, 0.2f, 1f);

	private static readonly FieldRef<Character, ZNetView> CharacterNViewRef = AccessTools.FieldRefAccess<Character, ZNetView>("m_nview");

	private static readonly FieldRef<GuiBar, RectTransform> GuiBarRectRef = AccessTools.FieldRefAccess<GuiBar, RectTransform>("m_bar");

	private static readonly FieldRef<GuiBar, float> GuiBarWidthRef = AccessTools.FieldRefAccess<GuiBar, float>("m_width");

	private static readonly FieldRef<Player, float> PlayerStaminaRef = AccessTools.FieldRefAccess<Player, float>("m_stamina");

	private static readonly FieldRef<Player, float> PlayerStaminaRegenTimerRef = AccessTools.FieldRefAccess<Player, float>("m_staminaRegenTimer");

	private static bool wasDebtActiveLastUpdate;

	private static bool wasDebtHudActiveLastUpdate;

	private static int lastLoggedDebtWholeValue = int.MinValue;

	private static int lastLoggedBlockedSpendWholeValue = int.MinValue;

	private static float lastLoggedBlockedSpendAmount = float.NaN;

	internal static void ResetTracking()
	{
		wasDebtActiveLastUpdate = false;
		wasDebtHudActiveLastUpdate = false;
		lastLoggedDebtWholeValue = int.MinValue;
		lastLoggedBlockedSpendWholeValue = int.MinValue;
		lastLoggedBlockedSpendAmount = float.NaN;
	}

	internal static bool IsFinite(float value)
	{
		if (!float.IsNaN(value))
		{
			return !float.IsInfinity(value);
		}
		return false;
	}

	internal static bool ShouldManage(Player player)
	{
		if (StridePlugin.StaminaDebtEnabled == null || !StridePlugin.StaminaDebtEnabled.Value)
		{
			return false;
		}
		if ((Object)(object)player == (Object)null)
		{
			return false;
		}
		ZNetView val = CharacterNViewRef.Invoke((Character)(object)player);
		if ((Object)(object)player == (Object)(object)Player.m_localPlayer && (Object)(object)val != (Object)null && val.IsValid())
		{
			return val.IsOwner();
		}
		return false;
	}

	internal static float GetCurrentStamina(Player player)
	{
		if (!((Object)(object)player != (Object)null))
		{
			return 0f;
		}
		return PlayerStaminaRef.Invoke(player);
	}

	internal static bool IsInDebt(Player player)
	{
		if ((Object)(object)player != (Object)null)
		{
			return PlayerStaminaRef.Invoke(player) < 0f;
		}
		return false;
	}

	internal static bool CanBorrowForChunkedUse(Player player, float amount)
	{
		if ((Object)(object)player == (Object)null || !IsFinite(amount) || amount < 5f)
		{
			return false;
		}
		float num = PlayerStaminaRef.Invoke(player);
		if (num > 0f)
		{
			return amount > num;
		}
		return false;
	}

	internal static void ApplyDebt(Player player, float amount)
	{
		if (!((Object)(object)player == (Object)null) && IsFinite(amount) && !(amount <= 0f))
		{
			float num = PlayerStaminaRef.Invoke(player);
			PlayerStaminaRef.Invoke(player) -= amount;
			PlayerStaminaRegenTimerRef.Invoke(player) = 0f;
			Log($"Borrowed stamina debt. Cost {amount:0.###}, stamina {num:0.###} -> {PlayerStaminaRef.Invoke(player):0.###}.");
		}
	}

	internal static void ClearRegenDelay(Player player)
	{
		PlayerStaminaRegenTimerRef.Invoke(player) = 0f;
	}

	internal static void ApplyHudDebtVisuals(Hud hud, Player player)
	{
		float num = PlayerStaminaRef.Invoke(player);
		float num2 = Mathf.Max(1f, ((Character)player).GetMaxStamina());
		float debtRatio = Mathf.Min(1.25f, (0f - num) / num2);
		hud.m_staminaText.text = StringExtensionMethods.ToFastString(Mathf.CeilToInt(num));
		SetDebtBar(hud.m_staminaBar2Fast, debtRatio);
		SetDebtBar(hud.m_staminaBar2Slow, debtRatio);
		wasDebtHudActiveLastUpdate = true;
	}

	internal static void RestoreHudVisuals(Hud hud)
	{
		if (wasDebtHudActiveLastUpdate)
		{
			RestoreBar(hud.m_staminaBar2Fast);
			RestoreBar(hud.m_staminaBar2Slow);
			wasDebtHudActiveLastUpdate = false;
		}
	}

	private static void SetDebtBar(GuiBar bar, float debtRatio)
	{
		//IL_001a: Unknown result type (might be due to invalid IL or missing references)
		//IL_003b: 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_0043: Unknown result type (might be due to invalid IL or missing references)
		//IL_0055: Unknown result type (might be due to invalid IL or missing references)
		if (!((Object)(object)bar == (Object)null))
		{
			if (((Behaviour)bar).enabled)
			{
				((Behaviour)bar).enabled = false;
			}
			bar.SetColor(DebtBarColor);
			RectTransform val = GuiBarRectRef.Invoke(bar);
			if ((Object)(object)val != (Object)null)
			{
				Vector3 localScale = ((Transform)val).localScale;
				localScale.x = 0f - Mathf.Abs(localScale.x);
				((Transform)val).localScale = localScale;
				val.SetSizeWithCurrentAnchors((Axis)0, GuiBarWidthRef.Invoke(bar) * debtRatio);
			}
		}
	}

	private static void RestoreBar(GuiBar bar)
	{
		//IL_0027: Unknown result type (might be due to invalid IL or missing references)
		//IL_002c: Unknown result type (might be due to invalid IL or missing references)
		//IL_002f: 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)
		if (!((Object)(object)bar == (Object)null))
		{
			bar.ResetColor();
			RectTransform val = GuiBarRectRef.Invoke(bar);
			if ((Object)(object)val != (Object)null)
			{
				Vector3 localScale = ((Transform)val).localScale;
				localScale.x = Mathf.Abs(localScale.x);
				((Transform)val).localScale = localScale;
			}
			if (!((Behaviour)bar).enabled)
			{
				((Behaviour)bar).enabled = true;
			}
		}
	}

	internal static void LogUpdate(Player player, float previousStamina)
	{
		if (!ShouldManage(player))
		{
			return;
		}
		float num = PlayerStaminaRef.Invoke(player);
		bool num2 = num < 0f;
		if (num2)
		{
			int num3 = Mathf.CeilToInt(num);
			if (!wasDebtActiveLastUpdate)
			{
				Log($"Debt active. Stamina crossed below zero: {previousStamina:0.###} -> {num:0.###}.");
			}
			else if (num3 != lastLoggedDebtWholeValue)
			{
				Log($"Debt repaying. Stamina {previousStamina:0.###} -> {num:0.###}, regen timer {PlayerStaminaRegenTimerRef.Invoke(player):0.###}.");
			}
			lastLoggedDebtWholeValue = num3;
		}
		else if (wasDebtActiveLastUpdate)
		{
			Log($"Debt cleared. Stamina {previousStamina:0.###} -> {num:0.###}.");
			lastLoggedDebtWholeValue = int.MinValue;
			lastLoggedBlockedSpendWholeValue = int.MinValue;
			lastLoggedBlockedSpendAmount = float.NaN;
		}
		wasDebtActiveLastUpdate = num2;
	}

	internal static void LogBlockedSpend(Player player, float amount)
	{
		if (!((Object)(object)player == (Object)null))
		{
			float num = PlayerStaminaRef.Invoke(player);
			int num2 = Mathf.CeilToInt(num);
			if (num2 != lastLoggedBlockedSpendWholeValue || !(Mathf.Abs(amount - lastLoggedBlockedSpendAmount) < 0.01f))
			{
				lastLoggedBlockedSpendWholeValue = num2;
				lastLoggedBlockedSpendAmount = amount;
				Log($"Blocked stamina spend while in debt. Cost {amount:0.###}, current stamina {num:0.###}.");
			}
		}
	}

	internal static void RestoreDebtAfterFoodUpdateClamp(Player player, float debtStamina)
	{
		float num = PlayerStaminaRef.Invoke(player);
		if (num == 0f)
		{
			PlayerStaminaRef.Invoke(player) = debtStamina;
			Log($"Restored stamina debt after food update clamp. Stamina {num:0.###} -> {debtStamina:0.###}.");
		}
	}

	private static void Log(string message)
	{
		if (StridePlugin.IsDebugEnabled(StridePlugin.DebugLogFeature.StaminaDebt))
		{
			ManualLogSource log = StridePlugin.Log;
			if (log != null)
			{
				log.LogInfo((object)("[StaminaDebt] " + message));
			}
		}
	}
}
internal static class StrideState
{
	internal struct WallContactSnapshot
	{
		internal Vector3 Normal;

		internal Vector3 ApproachVelocity;

		internal float ContactTime;

		internal float ApproachTime;

		internal float NearestDistance;

		internal static WallContactSnapshot Invalid
		{
			get
			{
				//IL_000a: Unknown result type (might be due to invalid IL or missing references)
				//IL_000f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				WallContactSnapshot result = default(WallContactSnapshot);
				result.Normal = Vector3.zero;
				result.ApproachVelocity = Vector3.zero;
				result.ContactTime = -999f;
				result.ApproachTime = -999f;
				result.NearestDistance = float.MaxValue;
				return result;
			}
		}
	}

	private static float lastGroundedTime = -999f;

	private static float lastGroundJumpTime = -999f;

	private static float lastJumpPressedTime = -999f;

	private static float lastWallJumpTime = -999f;

	private static float lastFallRollAttemptTime = -999f;

	private static float lastFallRollDownwardSpeed;

	private static bool suppressNextGroundJumpLog;

	private static bool usedGroundJumpSinceLastGrounded;

	private static bool usedWallJumpThisAirborneState;

	private static WallContactSnapshot lastWallContact = WallContactSnapshot.Invalid;

	internal static void RecordJumpPressed()
	{
		if (UsesBufferedJumpTracking())
		{
			lastJumpPressedTime = Time.time;
		}
	}

	internal static void RecordGrounded()
	{
		lastGroundedTime = Time.time;
		usedGroundJumpSinceLastGrounded = false;
		lastWallContact = WallContactSnapshot.Invalid;
	}

	internal static void RecordGroundJump()
	{
		lastGroundJumpTime = Time.time;
		usedGroundJumpSinceLastGrounded = true;
		lastJumpPressedTime = -999f;
		usedWallJumpThisAirborneState = false;
		lastWallContact = WallContactSnapshot.Invalid;
		if (suppressNextGroundJumpLog)
		{
			suppressNextGroundJumpLog = false;
		}
		else
		{
			LogJumpTiming("Recorded regular jump.");
		}
	}

	internal static void RecordWallJump()
	{
		lastWallJumpTime = Time.time;
		lastJumpPressedTime = -999f;
		usedWallJumpThisAirborneState = true;
		lastWallContact = WallContactSnapshot.Invalid;
	}

	internal static void RecordCoyoteJump()
	{
		lastGroundJumpTime = Time.time;
		usedGroundJumpSinceLastGrounded = true;
		usedWallJumpThisAirborneState = false;
		lastWallContact = WallContactSnapshot.Invalid;
	}

	internal static void RecordWallContact(Vector3 wallNormal, Vector3 horizontalVelocity, float nearestDistance)
	{
		//IL_0035: Unknown result type (might be due to invalid IL or missing references)
		//IL_003a: Unknown result type (might be due to invalid IL or missing references)
		//IL_0047: Unknown result type (might be due to invalid IL or missing references)
		//IL_004c: Unknown result type (might be due to invalid IL or missing references)
		//IL_00c5: 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_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_00a2: 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_00fc: Unknown result type (might be due to invalid IL or missing references)
		//IL_00fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_0103: Unknown result type (might be due to invalid IL or missing references)
		//IL_0104: Unknown result type (might be due to invalid IL or missing references)
		//IL_0141: Unknown result type (might be due to invalid IL or missing references)
		//IL_0142: Unknown result type (might be due to invalid IL or missing references)
		if (StridePlugin.WallJumpEnabled == null || !StridePlugin.WallJumpEnabled.Value)
		{
			lastWallContact = WallContactSnapshot.Invalid;
		}
		else if (!(((Vector3)(ref wallNormal)).sqrMagnitude < 0.01f))
		{
			float time = Time.time;
			Vector3 normalized = ((Vector3)(ref wallNormal)).normalized;
			if (!IsWallContactRecent() || !(Vector3.Dot(lastWallContact.Normal, normalized) >= 0.8f) || !(nearestDistance <= lastWallContact.NearestDistance + 0.12f))
			{
				WallContactSnapshot wallContactSnapshot = default(WallContactSnapshot);
				wallContactSnapshot.Normal = normalized;
				wallContactSnapshot.NearestDistance = nearestDistance;
				wallContactSnapshot.ContactTime = time;
				wallContactSnapshot.ApproachTime = -999f;
				wallContactSnapshot.ApproachVelocity = Vector3.zero;
				lastWallContact = wallContactSnapshot;
			}
			else
			{
				lastWallContact.ContactTime = time;
				lastWallContact.Normal = normalized;
				lastWallContact.NearestDistance = Mathf.Min(lastWallContact.NearestDistance, nearestDistance);
			}
			float magnitude = ((Vector3)(ref horizontalVelocity)).magnitude;
			float num = ((magnitude > 0.01f) ? Vector3.Dot(horizontalVelocity / magnitude, -normalized) : 0f);
			if (magnitude >= StridePlugin.MinimumWallJumpEntrySpeed && num >= 0.05f && magnitude > ((Vector3)(ref lastWallContact.ApproachVelocity)).magnitude)
			{
				lastWallContact.ApproachTime = time;
				lastWallContact.ApproachVelocity = horizontalVelocity;
			}
		}
	}

	internal static void RecordFallRollAttempt(float downwardSpeed)
	{
		if (StridePlugin.FallRollEnabled != null && StridePlugin.FallRollEnabled.Value)
		{
			lastFallRollAttemptTime = Time.time;
			lastFallRollDownwardSpeed = Mathf.Max(0f, downwardSpeed);
		}
	}

	private static void ClearFallRollAttempt()
	{
		lastFallRollAttemptTime = -999f;
		lastFallRollDownwardSpeed = 0f;
	}

	internal static void ResetAirborneState()
	{
		usedWallJumpThisAirborneState = false;
		lastWallContact = WallContactSnapshot.Invalid;
		ClearFallRollAttempt();
	}

	internal static void ResetAll()
	{
		lastGroundedTime = -999f;
		lastGroundJumpTime = -999f;
		lastJumpPressedTime = -999f;
		lastWallJumpTime = -999f;
		ClearFallRollAttempt();
		lastWallContact = WallContactSnapshot.Invalid;
		suppressNextGroundJumpLog = false;
		usedGroundJumpSinceLastGrounded = false;
		usedWallJumpThisAirborneState = false;
	}

	internal static bool TryConsumeFallRollDamageReduction(out float reduction, out float bufferedAge, out float downwardSpeed)
	{
		if (StridePlugin.FallRollEnabled == null || !StridePlugin.FallRollEnabled.Value)
		{
			reduction = 0f;
			bufferedAge = -1f;
			downwardSpeed = 0f;
			return false;
		}
		bufferedAge = Time.time - lastFallRollAttemptTime;
		downwardSpeed = lastFallRollDownwardSpeed;
		reduction = 0f;
		if (bufferedAge < 0f)
		{
			return false;
		}
		if (bufferedAge > StridePlugin.FallRollBufferSeconds)
		{
			ClearFallRollAttempt();
			return false;
		}
		reduction = StridePlugin.FallRollDamageReduction;
		ClearFallRollAttempt();
		return reduction > 0f;
	}

	private static bool HasPendingFallRollAttempt()
	{
		return lastFallRollAttemptTime > -100f;
	}

	internal static bool HasPendingFallRollAttemptForDebug()
	{
		return HasPendingFallRollAttempt();
	}

	private static void LogFallRollResult(string message)
	{
		if (StridePlugin.IsDebugEnabled(StridePlugin.DebugLogFeature.FallRoll))
		{
			ManualLogSource log = StridePlugin.Log;
			if (log != null)
			{
				log.LogInfo((object)("[FallRoll] " + message));
			}
		}
	}

	internal static bool TryGetWallJumpContact(Player player, bool hasCurrentWall, PlayerUpdateWallJumpPatch.WallDetectionResult currentWall, out WallContactSnapshot wallContact, out bool usingRememberedWall)
	{
		//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b9: 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_00ac: 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_00d4: Unknown result type (might be due to invalid IL or missing references)
		//IL_00de: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e3: Unknown result type (might be due to invalid IL or missing references)
		//IL_012c: Unknown result type (might be due to invalid IL or missing references)
		wallContact = default(WallContactSnapshot);
		usingRememberedWall = false;
		if (StridePlugin.WallJumpEnabled == null || !StridePlugin.WallJumpEna