Decompiled source of ClimbStamina v2.0.1

Mods/ClimbStamina.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BoneLib;
using BoneLib.BoneMenu;
using ClimbStamina;
using ClimbStamina.Compatibility;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSLZ.Bonelab;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Interaction;
using Il2CppSLZ.VRMK;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(ClimbStaminaMod), "ClimbStamina", "2.0.0", "Cesar", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: MelonAdditionalDependencies(new string[] { "BoneLib" })]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("ClimbStamina")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.4.0.0")]
[assembly: AssemblyInformationalVersion("1.4.0+9c48c483466f0f185405245372dbecef14d9152a")]
[assembly: AssemblyProduct("ClimbStamina")]
[assembly: AssemblyTitle("ClimbStamina")]
[assembly: AssemblyVersion("1.4.0.0")]
namespace ClimbStamina
{
	internal static class AscendCurve
	{
		private const float DeadZone = 0.3f;

		private const float BriskSpeed = 0.9f;

		private const float SprintSpeed = 3f;

		private const float SprintExtraScale = 3f;

		internal static float Factor(float verticalVelocity)
		{
			float num = Mathf.Max(0f, Prefs.AscendingMultiplier.Value - 1f);
			if (num <= 0f || verticalVelocity <= 0.3f)
			{
				return 1f;
			}
			float num2 = Mathf.Clamp01((verticalVelocity - 0.3f) / 0.59999996f);
			float num3 = Mathf.Clamp01((verticalVelocity - 0.9f) / 2.1f) * 2f;
			return 1f + num * (num2 + num3);
		}
	}
	internal sealed class ClimbDetector
	{
		private sealed class HandSlot
		{
			internal Hand Hand;

			internal Grip Grip;

			internal bool IsClimbing
			{
				get
				{
					if ((Object)(object)Hand != (Object)null)
					{
						return (Object)(object)Grip != (Object)null;
					}
					return false;
				}
			}

			internal void Clear()
			{
				Hand = null;
				Grip = null;
			}
		}

		private readonly HandSlot _left = new HandSlot();

		private readonly HandSlot _right = new HandSlot();

		internal Action<Hand, Grip> OnBlockedClimbAttach;

		internal Action<Hand> OnClimbAttached;

		internal Func<Hand, bool> CanStartClimb = (Hand _) => true;

		internal bool LeftClimbing
		{
			get
			{
				if (!_left.IsClimbing)
				{
					return ClimbAnythingCompatibility.LeftClimbing;
				}
				return true;
			}
		}

		internal bool RightClimbing
		{
			get
			{
				if (!_right.IsClimbing)
				{
					return ClimbAnythingCompatibility.RightClimbing;
				}
				return true;
			}
		}

		internal int ClimbingHandCount => (LeftClimbing ? 1 : 0) + (RightClimbing ? 1 : 0);

		internal void Subscribe()
		{
			Hooking.OnGripAttached += OnGripAttached;
			Hooking.OnGripDetached += OnGripDetached;
		}

		internal void Unsubscribe()
		{
			Hooking.OnGripAttached -= OnGripAttached;
			Hooking.OnGripDetached -= OnGripDetached;
		}

		internal static bool IsClimbingGrip(Grip grip)
		{
			try
			{
				if ((Object)(object)grip == (Object)null)
				{
					return false;
				}
				if ((Object)(object)((Il2CppObjectBase)grip).TryCast<UIInteractionVolume>() != (Object)null)
				{
					return false;
				}
				if ((Object)(object)((Il2CppObjectBase)grip).TryCast<WorldGrip>() != (Object)null)
				{
					return true;
				}
				if (!((HandReciever)grip).HasHost)
				{
					return true;
				}
				if (((HandReciever)grip).IsStatic)
				{
					return true;
				}
				IGrippable host = ((HandReciever)grip).Host;
				Rigidbody val = ((host != null) ? host.Rb : null);
				return (Object)(object)val != (Object)null && val.isKinematic;
			}
			catch
			{
				return false;
			}
		}

		private void OnGripAttached(Grip grip, Hand hand)
		{
			if (!PlayerTracker.IsLocalHand(hand) || !IsClimbingGrip(grip))
			{
				return;
			}
			if (!CanStartClimb(hand))
			{
				OnBlockedClimbAttach?.Invoke(hand, grip);
				return;
			}
			HandSlot handSlot = SlotFor(hand);
			if (handSlot != null)
			{
				handSlot.Hand = hand;
				handSlot.Grip = grip;
				ClimbStaminaMod.LogDebug("Climb attach: " + SideName(hand) + " -> " + ((Object)grip).name);
				OnClimbAttached?.Invoke(hand);
			}
		}

		private void OnGripDetached(Grip grip, Hand hand)
		{
			if (!((Object)(object)hand == (Object)null))
			{
				HandSlot handSlot = SlotFor(hand);
				if (handSlot != null && handSlot.IsClimbing && !((Object)(object)handSlot.Hand != (Object)(object)hand) && ((Object)(object)handSlot.Grip == (Object)(object)grip || (Object)(object)grip == (Object)null))
				{
					ClimbStaminaMod.LogDebug("Climb detach: " + SideName(hand));
					handSlot.Clear();
				}
			}
		}

		internal void Validate()
		{
			ValidateSlot(_left);
			ValidateSlot(_right);
			ClimbAnythingCompatibility.Validate();
		}

		private static void ValidateSlot(HandSlot slot)
		{
			if (!slot.IsClimbing)
			{
				if ((Object)(object)slot.Hand != (Object)null || (Object)(object)slot.Grip != (Object)null)
				{
					slot.Clear();
				}
			}
			else if ((Object)(object)slot.Hand == (Object)null || (Object)(object)slot.Grip == (Object)null || !PlayerTracker.IsLocalHand(slot.Hand) || (Object)(object)slot.Hand.AttachedReceiver != (Object)(object)slot.Grip)
			{
				slot.Clear();
			}
		}

		internal void Rescan()
		{
			RescanHand(Player.LeftHand, _left);
			RescanHand(Player.RightHand, _right);
		}

		private static void RescanHand(Hand hand, HandSlot slot)
		{
			if (slot.IsClimbing || (Object)(object)hand == (Object)null || !PlayerTracker.IsLocalHand(hand))
			{
				return;
			}
			HandReciever attachedReceiver = hand.AttachedReceiver;
			if (!((Object)(object)attachedReceiver == (Object)null))
			{
				Grip val = ((Il2CppObjectBase)attachedReceiver).TryCast<Grip>();
				if (!((Object)(object)val == (Object)null) && IsClimbingGrip(val))
				{
					slot.Hand = hand;
					slot.Grip = val;
				}
			}
		}

		internal void ForEachClimbingHand(Action<Hand> action)
		{
			if (_left.IsClimbing)
			{
				action(_left.Hand);
			}
			if (_right.IsClimbing)
			{
				action(_right.Hand);
			}
		}

		internal void Clear()
		{
			_left.Clear();
			_right.Clear();
			ClimbAnythingCompatibility.ClearState();
		}

		private HandSlot SlotFor(Hand hand)
		{
			//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_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Invalid comparison between Unknown and I4
			Handedness handedness = hand.handedness;
			if ((int)handedness != 1)
			{
				if ((int)handedness == 2)
				{
					return _right;
				}
				return null;
			}
			return _left;
		}

		private static string SideName(Hand hand)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Invalid comparison between Unknown and I4
			if ((int)hand.handedness != 1)
			{
				return "right";
			}
			return "left";
		}
	}
	public class ClimbStaminaMod : MelonMod
	{
		internal static bool ClimbBlocked;

		internal static bool LeftClimbBlocked;

		internal static bool RightClimbBlocked;

		private static Instance _logger;

		private static HandGaugeHud _gaugesStatic;

		private ClimbDetector _detector;

		private GripController _grips;

		private StaminaController _stamina;

		private HandGaugeHud _gauges;

		private TwoHandedController _twoHanded;

		private GripSlipController _slips;

		private static TwoHandedController _twoHandedStatic;

		private float _wgjMirror = -1f;

		private float _wgjLeftHeldAt = -10f;

		private float _wgjRightHeldAt = -10f;

		private const float WgjHoldWindowSeconds = 0.15f;

		private HapticsController _haptics;

		private LedgeDetector _ledges;

		private PanicGrabController _panic;

		private bool _wasEnabled = true;

		private Action<LevelInfo> _onLevelLoaded;

		private Action _onLevelUnloaded;

		private Action<Avatar> _onAvatarSwitched;

		private Action<RigManager> _onPlayerDeath;

		private Action<RigManager> _onPlayerResurrected;

		public override void OnInitializeMelon()
		{
			_logger = ((MelonBase)this).LoggerInstance;
			Prefs.Setup();
			SettingsMenu.Setup();
			_detector = new ClimbDetector();
			_grips = new GripController();
			_ledges = new LedgeDetector();
			_stamina = new StaminaController(_detector, _grips, _ledges);
			_gauges = new HandGaugeHud();
			_gaugesStatic = _gauges;
			_haptics = new HapticsController(_detector);
			_twoHanded = new TwoHandedController(_detector, _grips, _ledges, _haptics);
			_slips = new GripSlipController(_haptics);
			_twoHandedStatic = _twoHanded;
			_detector.CanStartClimb = (Hand hand) => !_twoHanded.IsHandBlocked(hand);
			_panic = new PanicGrabController(_detector, _stamina, _haptics);
			_detector.OnBlockedClimbAttach = OnBlockedClimbAttach;
			_detector.OnClimbAttached = _panic.OnClimbAttached;
			_detector.Subscribe();
			GripController.ApplyPatches(((MelonBase)this).HarmonyInstance);
			_onLevelLoaded = OnLevelLoaded;
			_onLevelUnloaded = OnLevelUnloaded;
			_onAvatarSwitched = delegate
			{
				PlayerTracker.RefreshAvatarMultiplier();
			};
			_onPlayerDeath = delegate(RigManager rig)
			{
				if (IsLocalRig(rig))
				{
					_detector.Clear();
					_grips.Clear();
					_panic.Reset();
					_ledges.Reset();
				}
			};
			_onPlayerResurrected = delegate(RigManager rig)
			{
				if (IsLocalRig(rig))
				{
					_stamina.ResetFull();
					_twoHanded.ResetFull();
					_wgjMirror = -1f;
				}
			};
			Hooking.OnLevelLoaded += _onLevelLoaded;
			Hooking.OnLevelUnloaded += _onLevelUnloaded;
			Hooking.OnSwitchAvatarPostfix += _onAvatarSwitched;
			Hooking.OnPlayerDeath += _onPlayerDeath;
			Hooking.OnPlayerResurrected += _onPlayerResurrected;
			Log("BoneLib detected.");
			FusionCompatibility.Detect();
			ClimbAnythingCompatibility.OnClimbAttached = _panic.OnClimbAttached;
			ClimbAnythingCompatibility.Initialize(((MelonBase)this).HarmonyInstance);
			Log("Initialized.");
		}

		public override void OnLateInitializeMelon()
		{
			ClimbAnythingCompatibility.RetryInitialize();
		}

		public override void OnDeinitializeMelon()
		{
			_gauges?.Drop();
			_detector?.Unsubscribe();
			Hooking.OnLevelLoaded -= _onLevelLoaded;
			Hooking.OnLevelUnloaded -= _onLevelUnloaded;
			Hooking.OnSwitchAvatarPostfix -= _onAvatarSwitched;
			Hooking.OnPlayerDeath -= _onPlayerDeath;
			Hooking.OnPlayerResurrected -= _onPlayerResurrected;
		}

		public override void OnFixedUpdate()
		{
			if (_slips != null && Prefs.Enabled.Value)
			{
				_slips.FixedTick(Time.fixedDeltaTime);
			}
		}

		public override void OnUpdate()
		{
			if (_stamina == null)
			{
				return;
			}
			bool value = Prefs.Enabled.Value;
			if (value != _wasEnabled)
			{
				_wasEnabled = value;
				if (!value)
				{
					_detector.Clear();
					_grips.Clear();
					_stamina.ResetFull();
					_twoHanded.ResetFull();
					_gauges.Drop();
					_haptics.Reset();
					_slips.Reset();
					_panic.Reset();
					_ledges.Reset();
				}
				else if (PlayerTracker.IsValid)
				{
					_detector.Rescan();
				}
			}
			if (value)
			{
				float deltaTime = Time.deltaTime;
				_panic.Update(deltaTime);
				_gauges.Prewarm();
				_twoHanded.Update(deltaTime);
				if (_detector.LeftClimbing)
				{
					_wgjLeftHeldAt = Time.time;
				}
				if (_detector.RightClimbing)
				{
					_wgjRightHeldAt = Time.time;
				}
				float stamina = _stamina.Stamina;
				if (_wgjMirror >= 0f && stamina < _wgjMirror - 0.0001f)
				{
					_twoHanded.ApplyJumpSpend(_wgjMirror - stamina, Time.time - _wgjLeftHeldAt < 0.15f, Time.time - _wgjRightHeldAt < 0.15f);
				}
				float num = Mathf.Min(_twoHanded.LeftStamina, _twoHanded.RightStamina);
				_stamina.MirrorPool(num);
				_wgjMirror = num;
				_gauges.UpdateTwoHanded(_twoHanded.LeftFraction, _twoHanded.LeftExhausted, _twoHanded.LeftDraining, _twoHanded.RightFraction, _twoHanded.RightExhausted, _twoHanded.RightDraining, deltaTime);
				_haptics.UpdateTwoHanded(_twoHanded.LeftFraction, _twoHanded.LeftDraining, _twoHanded.LeftSecondsToExhaustion, _twoHanded.RightFraction, _twoHanded.RightDraining, _twoHanded.RightSecondsToExhaustion, deltaTime);
				bool leftDraining = _twoHanded.LeftDraining;
				bool rightDraining = _twoHanded.RightDraining;
				float num2 = 1f;
				float secondsLeft = float.PositiveInfinity;
				if (leftDraining)
				{
					num2 = _twoHanded.LeftFraction;
					secondsLeft = _twoHanded.LeftSecondsToExhaustion;
				}
				if (rightDraining && _twoHanded.RightFraction < num2)
				{
					num2 = _twoHanded.RightFraction;
					secondsLeft = _twoHanded.RightSecondsToExhaustion;
				}
				_slips.Update(leftDraining || rightDraining, num2, secondsLeft, leftDraining, rightDraining, deltaTime);
			}
		}

		private void OnLevelLoaded(LevelInfo info)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			_detector.Clear();
			_grips.Clear();
			_stamina.ResetFull();
			_twoHanded.ResetFull();
			_gauges.Drop();
			_haptics.Reset();
			_slips.Reset();
			_panic.Reset();
			_ledges.Reset();
			PlayerTracker.RefreshAvatarMultiplier();
			Log("BONELAB player rig acquired (" + info.title + ").");
		}

		private void OnLevelUnloaded()
		{
			_detector.Clear();
			_grips.Clear();
			_gauges.Drop();
			_haptics.Reset();
			_slips.Reset();
			_panic.Reset();
			_ledges.Reset();
		}

		private void OnBlockedClimbAttach(Hand hand, Grip grip)
		{
			_grips.QueueDetach(hand);
		}

		private static bool IsLocalRig(RigManager rig)
		{
			RigManager rigManager = Player.RigManager;
			if ((Object)(object)rig != (Object)null && (Object)(object)rigManager != (Object)null)
			{
				return (Object)(object)rig == (Object)(object)rigManager;
			}
			return false;
		}

		internal static void Log(string message)
		{
			Instance logger = _logger;
			if (logger != null)
			{
				logger.Msg(message);
			}
		}

		internal static void LogWarning(string message)
		{
			Instance logger = _logger;
			if (logger != null)
			{
				logger.Warning(message);
			}
		}

		internal static void FlashGauges()
		{
			_gaugesStatic?.FlashPanic();
		}

		internal static void RoutePanicCost(Hand hand)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			if (_twoHandedStatic != null && (Object)(object)hand != (Object)null)
			{
				_twoHandedStatic.ApplyPanicGrab(hand.handedness);
			}
		}

		internal static void ReapplyGaugePose()
		{
			_gaugesStatic?.ReapplyPose();
		}

		internal static void LogDebug(string message)
		{
			if (Prefs.DebugLogging != null && Prefs.DebugLogging.Value)
			{
				Instance logger = _logger;
				if (logger != null)
				{
					logger.Msg("[debug] " + message);
				}
			}
		}
	}
	internal sealed class GripController
	{
		private readonly List<Hand> _pendingDetach = new List<Hand>(2);

		internal static void ReleaseClimbingHand(Hand hand)
		{
			if ((Object)(object)hand == (Object)null)
			{
				return;
			}
			HandReciever attachedReceiver = hand.AttachedReceiver;
			if (!((Object)(object)attachedReceiver == (Object)null))
			{
				Grip val = ((Il2CppObjectBase)attachedReceiver).TryCast<Grip>();
				if (!((Object)(object)val == (Object)null) && ClimbDetector.IsClimbingGrip(val))
				{
					val.ForceDetach(hand);
					ClimbStaminaMod.LogDebug("Force-detached climbing hand");
				}
			}
		}

		internal static void SweepClimbingHands()
		{
			Hand leftHand = Player.LeftHand;
			if ((Object)(object)leftHand != (Object)null && PlayerTracker.IsLocalHand(leftHand))
			{
				ReleaseClimbingHand(leftHand);
			}
			Hand rightHand = Player.RightHand;
			if ((Object)(object)rightHand != (Object)null && PlayerTracker.IsLocalHand(rightHand))
			{
				ReleaseClimbingHand(rightHand);
			}
		}

		internal void QueueDetach(Hand hand)
		{
			if ((Object)(object)hand != (Object)null && !_pendingDetach.Contains(hand))
			{
				_pendingDetach.Add(hand);
			}
		}

		internal void ProcessQueue()
		{
			if (_pendingDetach.Count == 0)
			{
				return;
			}
			for (int i = 0; i < _pendingDetach.Count; i++)
			{
				Hand val = _pendingDetach[i];
				if ((Object)(object)val != (Object)null && PlayerTracker.IsLocalHand(val))
				{
					ReleaseClimbingHand(val);
				}
			}
			_pendingDetach.Clear();
		}

		internal void Clear()
		{
			_pendingDetach.Clear();
		}

		internal static void ApplyPatches(Harmony harmony)
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Expected O, but got Unknown
			MethodInfo method = typeof(Grip).GetMethod("OnGrabConfirm", AccessTools.all);
			MethodInfo method2 = typeof(GripController).GetMethod("OnGrabConfirmPrefix", AccessTools.all);
			if (method == null || method2 == null)
			{
				ClimbStaminaMod.LogWarning("Grip.OnGrabConfirm not found; exhausted grabs will be detached a frame late instead of blocked.");
			}
			else
			{
				harmony.Patch((MethodBase)method, new HarmonyMethod(method2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static bool OnGrabConfirmPrefix(Grip __instance, Hand hand)
		{
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Invalid comparison between Unknown and I4
			try
			{
				if (!ClimbStaminaMod.ClimbBlocked && !ClimbStaminaMod.LeftClimbBlocked && !ClimbStaminaMod.RightClimbBlocked)
				{
					return true;
				}
				if ((Object)(object)hand == (Object)null || !PlayerTracker.IsLocalHand(hand))
				{
					return true;
				}
				if ((Object)(object)__instance == (Object)null || !ClimbDetector.IsClimbingGrip(__instance))
				{
					return true;
				}
				if (!ClimbStaminaMod.ClimbBlocked && !(((int)hand.handedness == 1) ? ClimbStaminaMod.LeftClimbBlocked : ClimbStaminaMod.RightClimbBlocked))
				{
					return true;
				}
				ClimbStaminaMod.LogDebug("Blocked climbing grab while exhausted");
				return false;
			}
			catch (Exception ex)
			{
				ClimbStaminaMod.LogDebug("Grab prefix error, failing open: " + ex.GetType().Name);
				return true;
			}
		}
	}
	internal sealed class GripSlipController
	{
		private const float SlipFraction = 0.1f;

		private const float DesperateFraction = 0.05f;

		private const float SlipSecondsLeft = 3f;

		private const float DesperateSecondsLeft = 1.5f;

		private const float FirstSlipMin = 0.25f;

		private const float FirstSlipMax = 0.5f;

		private const float IntervalMin = 0.9f;

		private const float IntervalMax = 1.4f;

		private const float DesperateIntervalMin = 0.55f;

		private const float DesperateIntervalMax = 0.85f;

		private const float DepthMin = 0.015f;

		private const float DepthMax = 0.03f;

		private const float DesperateDepthMax = 0.035f;

		private const float HardDepthCap = 0.04f;

		private const float StrongSlipChance = 0.15f;

		private const float SlipTimeMin = 0.1f;

		private const float SlipTimeMax = 0.18f;

		private const float MinRecoverySeconds = 0.4f;

		private readonly HapticsController _haptics;

		private readonly Random _rng = new Random();

		private float _nextSlipIn = -1f;

		private float _slipSpeed;

		private float _slipRemaining;

		private float _slipDuration;

		private bool _slipStarted;

		internal GripSlipController(HapticsController haptics)
		{
			_haptics = haptics;
		}

		internal void Reset()
		{
			_nextSlipIn = -1f;
			_slipSpeed = 0f;
			_slipRemaining = 0f;
			_slipStarted = false;
		}

		internal void Update(bool drainingClimb, float fraction, float secondsLeft, bool leftHand, bool rightHand, float deltaTime)
		{
			bool flag = fraction <= 0.1f || secondsLeft <= 3f;
			if (!Prefs.CrumblingGrip.Value || !drainingClimb || !flag || fraction <= 0f)
			{
				Reset();
				return;
			}
			bool flag2 = fraction <= 0.05f || secondsLeft <= 1.5f;
			if (_nextSlipIn < 0f)
			{
				_nextSlipIn = Range(0.25f, 0.5f);
			}
			_nextSlipIn -= deltaTime;
			if (!(_nextSlipIn > 0f) && !(_slipRemaining > 0f))
			{
				float max = (flag2 ? 0.035f : 0.03f);
				float num = Range(0.015f, max);
				if (flag2 && (float)_rng.NextDouble() < 0.15f)
				{
					num = 0.04f;
				}
				num = Mathf.Min(num, 0.04f);
				_slipDuration = Range(0.1f, 0.18f);
				_slipRemaining = _slipDuration;
				_slipSpeed = num / _slipDuration;
				_slipStarted = false;
				float scale = ((leftHand && rightHand) ? 0.8f : 1f);
				if (leftHand)
				{
					_haptics.PulseSlip((Handedness)1, scale);
				}
				if (rightHand)
				{
					_haptics.PulseSlip((Handedness)2, scale);
				}
				_nextSlipIn = Mathf.Max(Range(flag2 ? 0.55f : 0.9f, flag2 ? 0.85f : 1.4f), _slipDuration + 0.4f);
				ClimbStaminaMod.LogDebug($"Grip slip: {num * 100f:0.0} cm over {_slipDuration * 1000f:0} ms");
			}
		}

		internal void FixedTick(float fixedDeltaTime)
		{
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a9: 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 (_slipRemaining <= 0f || fixedDeltaTime <= 0f)
			{
				return;
			}
			_slipRemaining -= fixedDeltaTime;
			bool flag = _slipRemaining <= 0f;
			try
			{
				PhysicsRig physicsRig = Player.PhysicsRig;
				PhysTorso val = (((Object)(object)physicsRig != (Object)null) ? physicsRig.torso : null);
				if ((Object)(object)val == (Object)null)
				{
					_slipRemaining = 0f;
					return;
				}
				Rigidbody rbPelvis = val.rbPelvis;
				Rigidbody rbChest = val.rbChest;
				if (!_slipStarted)
				{
					_slipStarted = true;
					Vector3 val2 = Vector3.down * _slipSpeed;
					if ((Object)(object)rbPelvis != (Object)null && !rbPelvis.isKinematic)
					{
						rbPelvis.AddForce(val2, (ForceMode)2);
					}
					if ((Object)(object)rbChest != (Object)null && !rbChest.isKinematic)
					{
						rbChest.AddForce(val2, (ForceMode)2);
					}
				}
				if (flag)
				{
					CatchBody(rbPelvis);
					CatchBody(rbChest);
				}
			}
			catch (Exception ex)
			{
				_slipRemaining = 0f;
				ClimbStaminaMod.LogDebug("Grip slip aborted (" + ex.GetType().Name + ")");
			}
		}

		private void CatchBody(Rigidbody body)
		{
			//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_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)body == (Object)null) && !body.isKinematic)
			{
				float num = Mathf.Min(Vector3.Dot(body.velocity, Vector3.down), _slipSpeed);
				if (num > 0f)
				{
					body.AddForce(Vector3.up * num, (ForceMode)2);
				}
			}
		}

		private float Range(float min, float max)
		{
			return min + (float)_rng.NextDouble() * (max - min);
		}
	}
	internal sealed class HandGaugeHud
	{
		private sealed class Gauge
		{
			public float ShownGlowAlpha = -1f;

			public GameObject Root;

			public CanvasGroup Group;

			public Image Fill;

			public Image Glow;

			public Image Hatch;

			public Image Hairline;

			public RectTransform MovingCap;

			public Image MovingCapImage;

			public Image AnchorCap;

			public TextMeshProUGUI Value;

			public TextMeshProUGUI Chip;

			public Image ChipFrame;

			public float Alpha;

			public float FullSince = -999f;

			public int ShownPercent = -2;

			public int ShownState = -1;

			public bool Left;
		}

		private enum HandState
		{
			Ready,
			Regen,
			Draining,
			Critical,
			Lockout
		}

		private const float RectW = 300f;

		private const float RectH = 190f;

		private static readonly Vector2 ArcCenter = new Vector2(0f, -73f);

		private const float ArcRadius = 118f;

		private const float ArcWidth = 12f;

		private const float HairlineRadius = 128f;

		private const float HatchRadius = 106f;

		private const float HatchWidth = 26f;

		private const float BaseScale = 0.00028333333f;

		private const int Tex = 512;

		private const float TexSpan = 300f;

		private static readonly Color TrackColor = FromHex(1915460, 1f);

		private static readonly Color TickColor = FromHex(658963, 0.85f);

		private static readonly Color LockoutTone = FromHex(4873064, 1f);

		private static readonly Color HandLabelColor = FromHex(7177364, 1f);

		private static readonly Color[] _ramp = BuildRamp();

		private const float FadeRate = 7f;

		private const float FullLingerSeconds = 1.2f;

		private readonly Gauge _left = new Gauge
		{
			Left = true
		};

		private readonly Gauge _right = new Gauge
		{
			Left = false
		};

		private readonly string[] _numbers;

		private Texture2D _trackTex;

		private Texture2D _fillTex;

		private Texture2D _tickTex;

		private Texture2D _hairTex;

		private Texture2D _glowTex;

		private Texture2D _hatchTex;

		private Texture2D _capTex;

		private Texture2D _chipTex;

		private Sprite _trackSpr;

		private Sprite _fillSpr;

		private Sprite _tickSpr;

		private Sprite _hairSpr;

		private Sprite _glowSpr;

		private Sprite _hatchSpr;

		private Sprite _capSpr;

		private Sprite _chipSpr;

		private bool _assetsBuilt;

		private bool _assetsFailed;

		private bool _poseDirty;

		private static readonly string[] ChipText = new string[5] { "READY", "REGEN", "DRAINING", "CRITICAL", "LOCKOUT" };

		private float _panicFlash;

		internal HandGaugeHud()
		{
			_numbers = new string[101];
			for (int i = 0; i <= 100; i++)
			{
				_numbers[i] = i.ToString();
			}
		}

		internal void Prewarm()
		{
			if (!_assetsFailed && Prefs.HandGauges.Value && PlayerTracker.IsValid && (_assetsBuilt || BuildAssets()))
			{
				PrewarmGauge(_left, Player.LeftHand);
				PrewarmGauge(_right, Player.RightHand);
			}
		}

		private void PrewarmGauge(Gauge g, Hand hand)
		{
			if ((Object)(object)g.Root != (Object)null || (Object)(object)hand == (Object)null)
			{
				return;
			}
			try
			{
				if (Build(g, hand))
				{
					g.Root.SetActive(false);
				}
			}
			catch (Exception ex)
			{
				ClimbStaminaMod.LogDebug("Hand gauge prewarm failed (" + ex.GetType().Name + ")");
				DropGauge(g);
			}
		}

		internal void FlashPanic()
		{
			_panicFlash = 1f;
		}

		internal void ReapplyPose()
		{
			_poseDirty = true;
		}

		internal void Drop()
		{
			DropGauge(_left);
			DropGauge(_right);
			DestroyAssets();
			_panicFlash = 0f;
		}

		private static void DropGauge(Gauge g)
		{
			if ((Object)(object)g.Root != (Object)null)
			{
				Object.Destroy((Object)(object)g.Root);
			}
			g.Root = null;
			g.Group = null;
			g.Fill = null;
			g.Glow = null;
			g.Hatch = null;
			g.Hairline = null;
			g.MovingCap = null;
			g.MovingCapImage = null;
			g.AnchorCap = null;
			g.Value = null;
			g.Chip = null;
			g.ChipFrame = null;
			g.Alpha = 0f;
			g.FullSince = -999f;
			g.ShownPercent = -2;
			g.ShownState = -1;
		}

		internal void UpdateTwoHanded(float leftFraction, bool leftExhausted, bool leftDraining, float rightFraction, bool rightExhausted, bool rightDraining, float deltaTime)
		{
			Run(Prefs.HandGauges.Value && PlayerTracker.IsValid, leftFraction, leftExhausted, leftDraining, rightFraction, rightExhausted, rightDraining, deltaTime);
		}

		private void Run(bool wanted, float leftFraction, bool leftExhausted, bool leftClimbing, float rightFraction, bool rightExhausted, bool rightClimbing, float deltaTime)
		{
			bool wanted2 = wanted && GaugeWanted(_left, leftFraction, leftExhausted, leftClimbing);
			bool wanted3 = wanted && GaugeWanted(_right, rightFraction, rightExhausted, rightClimbing);
			_panicFlash = Mathf.Max(0f, _panicFlash - deltaTime * 1.6f);
			if (_poseDirty)
			{
				_poseDirty = false;
				if ((Object)(object)_left.Root != (Object)null && (Object)(object)Player.LeftHand != (Object)null)
				{
					Mount(_left, Player.LeftHand);
				}
				if ((Object)(object)_right.Root != (Object)null && (Object)(object)Player.RightHand != (Object)null)
				{
					Mount(_right, Player.RightHand);
				}
			}
			UpdateGauge(_left, Player.LeftHand, wanted2, leftFraction, leftExhausted, leftClimbing, deltaTime);
			UpdateGauge(_right, Player.RightHand, wanted3, rightFraction, rightExhausted, rightClimbing, deltaTime);
		}

		private static bool GaugeWanted(Gauge g, float fraction, bool exhausted, bool climbing)
		{
			if (fraction < 0.999f || exhausted || climbing)
			{
				g.FullSince = Time.time;
				return true;
			}
			return Time.time - g.FullSince < 1.2f;
		}

		private void UpdateGauge(Gauge g, Hand hand, bool wanted, float fraction, bool exhausted, bool handClimbing, float deltaTime)
		{
			//IL_0209: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Unknown result type (might be due to invalid IL or missing references)
			//IL_020e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0451: Unknown result type (might be due to invalid IL or missing references)
			//IL_0481: Unknown result type (might be due to invalid IL or missing references)
			//IL_0254: Unknown result type (might be due to invalid IL or missing references)
			//IL_025b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0262: Unknown result type (might be due to invalid IL or missing references)
			//IL_0276: Unknown result type (might be due to invalid IL or missing references)
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_0522: Unknown result type (might be due to invalid IL or missing references)
			//IL_0529: Unknown result type (might be due to invalid IL or missing references)
			//IL_0530: Unknown result type (might be due to invalid IL or missing references)
			//IL_0539: Unknown result type (might be due to invalid IL or missing references)
			//IL_02d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0367: Unknown result type (might be due to invalid IL or missing references)
			//IL_0394: Unknown result type (might be due to invalid IL or missing references)
			//IL_0313: Unknown result type (might be due to invalid IL or missing references)
			//IL_0326: Unknown result type (might be due to invalid IL or missing references)
			//IL_0330: Unknown result type (might be due to invalid IL or missing references)
			//IL_0335: Unknown result type (might be due to invalid IL or missing references)
			//IL_03af: Unknown result type (might be due to invalid IL or missing references)
			//IL_03b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_03bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c9: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)hand == (Object)null)
			{
				if ((Object)(object)g.Root != (Object)null)
				{
					DropGauge(g);
				}
				return;
			}
			float num = (wanted ? 1f : 0f);
			g.Alpha = Mathf.MoveTowards(g.Alpha, num, 7f * deltaTime);
			if (g.Alpha <= 0.001f)
			{
				if ((Object)(object)g.Root != (Object)null && g.Root.activeSelf)
				{
					g.Root.SetActive(false);
				}
				return;
			}
			if ((Object)(object)g.Root == (Object)null)
			{
				try
				{
					if (!Build(g, hand))
					{
						return;
					}
				}
				catch (Exception ex)
				{
					ClimbStaminaMod.LogDebug("Hand gauge build failed (" + ex.GetType().Name + ")");
					DropGauge(g);
					return;
				}
			}
			if ((Object)(object)g.Root.transform.parent != (Object)(object)((Component)hand).transform)
			{
				Mount(g, hand);
			}
			if (!g.Root.activeSelf)
			{
				g.Root.SetActive(true);
			}
			float time = Time.time;
			float num2 = 1f - 0.15f * Mathf.Clamp01(Mathf.Sin(time * 1.21f) * Mathf.Sin(time * 2.93f + 1.3f) * 1.6f - 0.45f);
			g.Group.alpha = g.Alpha * num2;
			HandState handState = (exhausted ? HandState.Lockout : ((fraction < 0.25f) ? HandState.Critical : (handClimbing ? HandState.Draining : ((fraction < 0.999f) ? HandState.Regen : HandState.Ready))));
			int num3 = Mathf.Clamp(Mathf.RoundToInt(fraction * 100f), 0, 100);
			bool flag = num3 != g.ShownPercent;
			bool flag2 = handState != (HandState)g.ShownState;
			if ((Object)(object)g.Value != (Object)null && ((Behaviour)g.Value).enabled != Prefs.ShowPercent.Value)
			{
				((Behaviour)g.Value).enabled = Prefs.ShowPercent.Value;
			}
			Color val = ((handState == HandState.Lockout) ? LockoutTone : _ramp[num3]);
			if (flag || flag2)
			{
				g.ShownPercent = num3;
				g.ShownState = (int)handState;
				g.Fill.fillAmount = 0.5f * fraction;
				float num4 = ((handState == HandState.Lockout) ? 0.45f : 1f);
				Color color = default(Color);
				((Color)(ref color))..ctor(val.r, val.g, val.b, num4);
				((Graphic)g.Fill).color = color;
				((Graphic)g.Hairline).color = val;
				bool flag3 = fraction > 0.004f;
				if (((Behaviour)g.AnchorCap).enabled != flag3)
				{
					((Behaviour)g.AnchorCap).enabled = flag3;
				}
				if (((Behaviour)g.MovingCapImage).enabled != flag3)
				{
					((Behaviour)g.MovingCapImage).enabled = flag3;
				}
				if (flag3)
				{
					((Graphic)g.AnchorCap).color = color;
					((Graphic)g.MovingCapImage).color = color;
					float num5 = (g.Left ? ((float)Math.PI * (1f - fraction)) : ((float)Math.PI * fraction));
					g.MovingCap.anchoredPosition = ArcCenter + new Vector2(Mathf.Cos(num5), Mathf.Sin(num5)) * 118f;
				}
				if ((Object)(object)g.Value != (Object)null)
				{
					((TMP_Text)g.Value).text = _numbers[num3];
					((Graphic)g.Value).color = val;
				}
				if ((Object)(object)g.Chip != (Object)null)
				{
					((TMP_Text)g.Chip).text = ChipText[(int)handState];
					((Graphic)g.Chip).color = val;
					if ((Object)(object)g.ChipFrame != (Object)null)
					{
						((Graphic)g.ChipFrame).color = new Color(val.r, val.g, val.b, 0.8f);
					}
				}
				bool flag4 = handState == HandState.Lockout;
				if (((Behaviour)g.Hatch).enabled != flag4)
				{
					((Behaviour)g.Hatch).enabled = flag4;
				}
			}
			float num6;
			switch (handState)
			{
			case HandState.Lockout:
			{
				float num7 = Mathf.Lerp(0.35f, 0.9f, 0.5f - 0.5f * Mathf.Cos(time * 3.4906588f));
				((Graphic)g.Glow).color = new Color(LockoutTone.r, LockoutTone.g, LockoutTone.b, num7 * 0.75f);
				((Graphic)g.Hatch).color = new Color(LockoutTone.r, LockoutTone.g, LockoutTone.b, num7);
				if (!((Behaviour)g.Glow).enabled)
				{
					((Behaviour)g.Glow).enabled = true;
				}
				return;
			}
			default:
				num6 = 0.16f;
				break;
			case HandState.Critical:
				num6 = 0.3f + 0.18f * Mathf.Sin(time * 5f);
				break;
			}
			float num8 = Mathf.Max(num6, _panicFlash * 0.8f);
			if (Mathf.Abs(num8 - g.ShownGlowAlpha) > 0.004f || flag || flag2)
			{
				g.ShownGlowAlpha = num8;
				if (!((Behaviour)g.Glow).enabled)
				{
					((Behaviour)g.Glow).enabled = true;
				}
				((Graphic)g.Glow).color = new Color(val.r, val.g, val.b, num8);
			}
		}

		private void Mount(Gauge g, Hand hand)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
			Transform transform = g.Root.transform;
			transform.SetParent(((Component)hand).transform, false);
			float num = ((!g.Left && Prefs.HandGaugeMirror.Value) ? (-1f) : 1f);
			transform.localPosition = new Vector3(Prefs.HandGaugePosX.Value * num, Prefs.HandGaugePosY.Value, Prefs.HandGaugePosZ.Value);
			transform.localRotation = Quaternion.Euler(Prefs.HandGaugeRotX.Value, Prefs.HandGaugeRotY.Value * num, Prefs.HandGaugeRotZ.Value * num);
			float num2 = 0.00028333333f * Mathf.Clamp(Prefs.HandGaugeScale.Value, 0.2f, 4f);
			transform.localScale = new Vector3(num2, num2, num2);
		}

		private bool Build(Gauge g, Hand hand)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			//IL_00a2: 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_00fe: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: 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_0190: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_01bc: 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_01f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_020d: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0278: Unknown result type (might be due to invalid IL or missing references)
			//IL_0287: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_02f0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ff: Unknown result type (might be due to invalid IL or missing references)
			//IL_0306: Expected O, but got Unknown
			//IL_0330: Unknown result type (might be due to invalid IL or missing references)
			//IL_0344: Unknown result type (might be due to invalid IL or missing references)
			//IL_0372: Unknown result type (might be due to invalid IL or missing references)
			//IL_038b: Unknown result type (might be due to invalid IL or missing references)
			//IL_039a: Unknown result type (might be due to invalid IL or missing references)
			if (_assetsFailed)
			{
				return false;
			}
			if (!_assetsBuilt && !BuildAssets())
			{
				return false;
			}
			g.Root = new GameObject(g.Left ? "ClimbStamina_GaugeL" : "ClimbStamina_GaugeR");
			Canvas obj = g.Root.AddComponent<Canvas>();
			obj.renderMode = (RenderMode)2;
			obj.sortingOrder = 100;
			g.Group = g.Root.AddComponent<CanvasGroup>();
			g.Group.interactable = false;
			g.Group.blocksRaycasts = false;
			g.Group.alpha = 0f;
			g.Root.GetComponent<RectTransform>().sizeDelta = new Vector2(300f, 190f);
			g.Glow = AddSquare(g.Root, "Glow", _glowSpr, new Color(0f, 0f, 0f, 0f));
			((Behaviour)g.Glow).enabled = false;
			AddSquare(g.Root, "Track", _trackSpr, TrackColor);
			Image val = AddSquare(g.Root, "Fill", _fillSpr, Color.white);
			val.type = (Type)3;
			val.fillMethod = (FillMethod)4;
			val.fillOrigin = ((!g.Left) ? 1 : 3);
			val.fillClockwise = g.Left;
			val.fillAmount = 0.5f;
			g.Fill = val;
			float num = (g.Left ? (-118f) : 118f);
			g.AnchorCap = AddDot(g.Root, "CapAnchor", ArcCenter + new Vector2(num, 0f));
			g.MovingCap = ((Component)(g.MovingCapImage = AddDot(g.Root, "CapMove", ArcCenter + new Vector2(0f - num, 0f)))).GetComponent<RectTransform>();
			AddSquare(g.Root, "Ticks", _tickSpr, TickColor);
			g.Hatch = AddSquare(g.Root, "Hatch", _hatchSpr, LockoutTone);
			((Behaviour)g.Hatch).enabled = false;
			g.Hairline = AddSquare(g.Root, "Hairline", _hairSpr, Color.white);
			TMP_FontAsset defaultFontAsset = TMP_Settings.defaultFontAsset;
			if ((Object)(object)defaultFontAsset != (Object)null)
			{
				g.Value = AddText(g.Root, "Value", defaultFontAsset, 46f, (FontStyles)1, new Vector2(0f, -12f), new Vector2(200f, 52f));
				TextMeshProUGUI obj2 = AddText(g.Root, "HandLabel", defaultFontAsset, 10f, (FontStyles)0, new Vector2(0f, -47f), new Vector2(200f, 14f));
				((TMP_Text)obj2).characterSpacing = 18f;
				((TMP_Text)obj2).text = (g.Left ? "LEFT HAND" : "RIGHT HAND");
				((Graphic)obj2).color = HandLabelColor;
				GameObject val2 = new GameObject("Chip");
				val2.transform.SetParent(g.Root.transform, false);
				RectTransform obj3 = val2.AddComponent<RectTransform>();
				obj3.sizeDelta = new Vector2(92f, 18f);
				obj3.anchoredPosition = new Vector2(0f, -70f);
				g.ChipFrame = val2.AddComponent<Image>();
				g.ChipFrame.sprite = _chipSpr;
				((Graphic)g.ChipFrame).color = Color.white;
				g.Chip = AddText(val2, "ChipText", defaultFontAsset, 11f, (FontStyles)0, Vector2.zero, new Vector2(92f, 18f));
				((TMP_Text)g.Chip).characterSpacing = 8f;
			}
			else
			{
				ClimbStaminaMod.LogDebug("No TMP font asset; hand gauges run arc-only");
			}
			g.ShownPercent = -2;
			g.ShownState = -1;
			g.ShownGlowAlpha = -1f;
			Mount(g, hand);
			ClimbStaminaMod.LogDebug($"Hand gauge built ({(g.Left ? "left" : "right")}); pose offset=({Prefs.HandGaugePosX.Value:0.###},{Prefs.HandGaugePosY.Value:0.###},{Prefs.HandGaugePosZ.Value:0.###}) euler=({Prefs.HandGaugeRotX.Value:0.#},{Prefs.HandGaugeRotY.Value:0.#},{Prefs.HandGaugeRotZ.Value:0.#}) " + "— tune via HandGaugePos*/HandGaugeRot*/HandGaugeScale in the config file.");
			return true;
		}

		private static Image AddSquare(GameObject parent, string name, Sprite sprite, Color color)
		{
			//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_0018: 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_0044: 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)
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent.transform, false);
			Image val2 = val.AddComponent<Image>();
			val2.sprite = sprite;
			((Graphic)val2).color = color;
			((Graphic)val2).raycastTarget = false;
			RectTransform component = val.GetComponent<RectTransform>();
			component.sizeDelta = new Vector2(300f, 300f);
			component.anchoredPosition = ArcCenter;
			return val2;
		}

		private Image AddDot(GameObject parent, string name, Vector2 pos)
		{
			//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_0018: 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_004c: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent.transform, false);
			Image val2 = val.AddComponent<Image>();
			val2.sprite = _capSpr;
			((Graphic)val2).raycastTarget = false;
			RectTransform component = val.GetComponent<RectTransform>();
			component.sizeDelta = new Vector2(12f, 12f);
			component.anchoredPosition = pos;
			return val2;
		}

		private static TextMeshProUGUI AddText(GameObject parent, string name, TMP_FontAsset font, float size, FontStyles style, Vector2 pos, Vector2 rectSize)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0007: Expected O, but got Unknown
			//IL_002e: 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_0067: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name);
			val.transform.SetParent(parent.transform, false);
			TextMeshProUGUI obj = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)obj).font = font;
			((TMP_Text)obj).fontSize = size;
			((TMP_Text)obj).fontStyle = style;
			((TMP_Text)obj).alignment = (TextAlignmentOptions)514;
			((TMP_Text)obj).enableWordWrapping = false;
			((Graphic)obj).raycastTarget = false;
			((TMP_Text)obj).text = string.Empty;
			RectTransform component = val.GetComponent<RectTransform>();
			component.sizeDelta = rectSize;
			component.anchoredPosition = pos;
			return obj;
		}

		private bool BuildAssets()
		{
			try
			{
				_trackSpr = MakeArcSprite(out _trackTex, 118f, 12f, roundCaps: true, 0f, 0f, 0f, endFade: false, 1f, radialFade: false, 0f);
				_fillSpr = MakeArcSprite(out _fillTex, 118f, 12f, roundCaps: false, 0f, 0f, 0f, endFade: false, 1f, radialFade: false, 0f);
				_tickSpr = MakeArcSprite(out _tickTex, 118f, 12f, roundCaps: false, 3f, 11f, -8.85f, endFade: false, 1f, radialFade: false, 0f);
				_hairSpr = MakeArcSprite(out _hairTex, 128f, 1.5f, roundCaps: false, 0f, 0f, 0f, endFade: true, 0.35f, radialFade: false, 0f);
				_glowSpr = MakeArcSprite(out _glowTex, 118f, 22f, roundCaps: true, 0f, 0f, 0f, endFade: false, 1f, radialFade: false, 10f);
				_hatchSpr = MakeArcSprite(out _hatchTex, 106f, 26f, roundCaps: false, 6f, 10f, -9.5f, endFade: false, 1f, radialFade: true, 0f);
				_capSpr = MakeDotSprite(out _capTex);
				_chipSpr = MakeChipSprite(out _chipTex);
				_assetsBuilt = true;
				return true;
			}
			catch (Exception ex)
			{
				ClimbStaminaMod.LogDebug("Hand gauge asset generation failed (" + ex.GetType().Name + "); gauges disabled for this session");
				DestroyAssets();
				_assetsFailed = true;
				return false;
			}
		}

		private void DestroyAssets()
		{
			Object[] array = (Object[])(object)new Object[16]
			{
				(Object)_trackSpr,
				(Object)_fillSpr,
				(Object)_tickSpr,
				(Object)_hairSpr,
				(Object)_glowSpr,
				(Object)_hatchSpr,
				(Object)_capSpr,
				(Object)_chipSpr,
				(Object)_trackTex,
				(Object)_fillTex,
				(Object)_tickTex,
				(Object)_hairTex,
				(Object)_glowTex,
				(Object)_hatchTex,
				(Object)_capTex,
				(Object)_chipTex
			};
			foreach (Object val in array)
			{
				if (val != (Object)null)
				{
					Object.Destroy(val);
				}
			}
			_trackSpr = (_fillSpr = (_tickSpr = (_hairSpr = (_glowSpr = (_hatchSpr = (_capSpr = (_chipSpr = null)))))));
			_trackTex = (_fillTex = (_tickTex = (_hairTex = (_glowTex = (_hatchTex = (_capTex = (_chipTex = null)))))));
			_assetsBuilt = false;
		}

		private static Sprite MakeArcSprite(out Texture2D tex, float radius, float width, bool roundCaps, float dashOn, float dashOff, float dashShift, bool endFade, float bakedAlpha, bool radialFade, float soft)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			//IL_02b4: Unknown result type (might be due to invalid IL or missing references)
			//IL_02c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_025a: Unknown result type (might be due to invalid IL or missing references)
			//IL_025f: Unknown result type (might be due to invalid IL or missing references)
			tex = new Texture2D(512, 512, (TextureFormat)4, false);
			((Texture)tex).wrapMode = (TextureWrapMode)1;
			Color32[] array = (Color32[])(object)new Color32[262144];
			float num = 75f / 128f;
			float num2 = width * 0.5f;
			float num3 = (float)Math.PI * radius;
			for (int i = 0; i < 512; i++)
			{
				float num4 = ((float)i + 0.5f) * num - 150f;
				for (int j = 0; j < 512; j++)
				{
					float num5 = ((float)j + 0.5f) * num - 150f;
					float num6 = Mathf.Sqrt(num5 * num5 + num4 * num4);
					float num7;
					float num9;
					if (num4 >= 0f)
					{
						num7 = Mathf.Abs(num6 - radius);
						float num8 = Mathf.Atan2(num4, num5);
						num9 = ((float)Math.PI - num8) * radius;
					}
					else
					{
						if (!roundCaps)
						{
							array[i * 512 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)0);
							continue;
						}
						float num10 = Mathf.Sqrt((num5 + radius) * (num5 + radius) + num4 * num4);
						float num11 = Mathf.Sqrt((num5 - radius) * (num5 - radius) + num4 * num4);
						if (num10 <= num11)
						{
							num7 = num10;
							num9 = 0f;
						}
						else
						{
							num7 = num11;
							num9 = num3;
						}
					}
					float num13;
					if (soft > 0f)
					{
						float num12 = Mathf.Max(0f, num7 - num2 + soft);
						num13 = Mathf.Exp((0f - num12 * num12) / (2f * soft * soft));
					}
					else
					{
						num13 = Mathf.Clamp01((num2 - num7) / num + 0.5f);
					}
					if (num13 > 0f && dashOn > 0f)
					{
						float num14 = (num9 + dashShift) % (dashOn + dashOff);
						if (num14 < 0f)
						{
							num14 += dashOn + dashOff;
						}
						if (num14 >= dashOn)
						{
							num13 = 0f;
						}
					}
					if (num13 > 0f && endFade)
					{
						float num15 = Mathf.Clamp01(num9 / num3);
						if (num15 < 0.18f)
						{
							num13 *= num15 / 0.18f;
						}
						else if (num15 > 0.82f)
						{
							num13 *= (1f - num15) / 0.18f;
						}
					}
					if (num13 > 0f && radialFade)
					{
						float num16 = Mathf.InverseLerp(108.6f, 87.4f, num6);
						num13 *= Mathf.Clamp01(num16);
					}
					byte b = (byte)Mathf.RoundToInt(Mathf.Clamp01(num13 * bakedAlpha) * 255f);
					array[i * 512 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, b);
				}
			}
			tex.SetPixels32(Il2CppStructArray<Color32>.op_Implicit(array));
			tex.Apply(false, true);
			return Sprite.Create(tex, new Rect(0f, 0f, 512f, 512f), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0);
		}

		private static Sprite MakeDotSprite(out Texture2D tex)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected O, but got Unknown
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e1: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Unknown result type (might be due to invalid IL or missing references)
			tex = new Texture2D(32, 32, (TextureFormat)4, false);
			((Texture)tex).wrapMode = (TextureWrapMode)1;
			Color32[] array = (Color32[])(object)new Color32[1024];
			float num = 15.5f;
			float num2 = 14.5f;
			for (int i = 0; i < 32; i++)
			{
				for (int j = 0; j < 32; j++)
				{
					float num3 = Mathf.Sqrt(((float)j - num) * ((float)j - num) + ((float)i - num) * ((float)i - num));
					byte b = (byte)Mathf.RoundToInt(Mathf.Clamp01(num2 - num3 + 0.5f) * 255f);
					array[i * 32 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, b);
				}
			}
			tex.SetPixels32(Il2CppStructArray<Color32>.op_Implicit(array));
			tex.Apply(false, true);
			return Sprite.Create(tex, new Rect(0f, 0f, 32f, 32f), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0);
		}

		private static Sprite MakeChipSprite(out Texture2D tex)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Expected O, but got Unknown
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: 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_0062: Unknown result type (might be due to invalid IL or missing references)
			tex = new Texture2D(92, 18, (TextureFormat)4, false);
			((Texture)tex).wrapMode = (TextureWrapMode)1;
			Color32[] array = (Color32[])(object)new Color32[1656];
			for (int i = 0; i < 18; i++)
			{
				for (int j = 0; j < 92; j++)
				{
					bool flag = j == 0 || i == 0 || j == 91 || i == 17;
					array[i * 92 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(flag ? byte.MaxValue : 0));
				}
			}
			tex.SetPixels32(Il2CppStructArray<Color32>.op_Implicit(array));
			tex.Apply(false, true);
			return Sprite.Create(tex, new Rect(0f, 0f, 92f, 18f), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0);
		}

		private static Color[] BuildRamp()
		{
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			(float, float, float, float)[] array = new(float, float, float, float)[4]
			{
				(0f, 0.62f, 0.22f, 28f),
				(0.14f, 0.72f, 0.19f, 35f),
				(0.5f, 0.85f, 0.16f, 95f),
				(1f, 0.82f, 0.13f, 195f)
			};
			Color[] array2 = (Color[])(object)new Color[101];
			for (int i = 0; i <= 100; i++)
			{
				float num = (float)i / 100f;
				int j;
				for (j = 1; j < array.Length - 1 && num > array[j].Item1; j++)
				{
				}
				(float, float, float, float) tuple = array[j - 1];
				(float, float, float, float) tuple2 = array[j];
				float num2 = Mathf.InverseLerp(tuple.Item1, tuple2.Item1, num);
				array2[i] = OklchToColor(Mathf.Lerp(tuple.Item2, tuple2.Item2, num2), Mathf.Lerp(tuple.Item3, tuple2.Item3, num2), Mathf.Lerp(tuple.Item4, tuple2.Item4, num2));
			}
			return array2;
		}

		private static Color OklchToColor(float L, float C, float Hdeg)
		{
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			float num = Hdeg * ((float)Math.PI / 180f);
			float num2 = C * Mathf.Cos(num);
			float num3 = C * Mathf.Sin(num);
			float num4 = L + 0.39633778f * num2 + 0.21580376f * num3;
			float num5 = L - 0.105561346f * num2 - 0.06385417f * num3;
			float num6 = L - 0.08948418f * num2 - 1.2914855f * num3;
			num4 = num4 * num4 * num4;
			num5 = num5 * num5 * num5;
			num6 = num6 * num6 * num6;
			float c = 4.0767417f * num4 - 3.3077116f * num5 + 0.23096994f * num6;
			float c2 = -1.268438f * num4 + 2.6097574f * num5 - 0.34131938f * num6;
			float c3 = -0.0041960864f * num4 - 0.7034186f * num5 + 1.7076147f * num6;
			return new Color(Gamma(c), Gamma(c2), Gamma(c3), 1f);
		}

		private static float Gamma(float c)
		{
			c = Mathf.Clamp01(c);
			if (!(c <= 0.0031308f))
			{
				return 1.055f * Mathf.Pow(c, 5f / 12f) - 0.055f;
			}
			return 12.92f * c;
		}

		private static Color FromHex(int rgb, float a)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			return new Color((float)((rgb >> 16) & 0xFF) / 255f, (float)((rgb >> 8) & 0xFF) / 255f, (float)(rgb & 0xFF) / 255f, a);
		}
	}
	internal sealed class HapticsController
	{
		private enum HandHapticState
		{
			None,
			Fatigue,
			Critical
		}

		private sealed class HandSchedule
		{
			public HandHapticState State;

			public float Now;

			public float NextPulseAt;

			public float CriticalSince;

			public float NextRefreshAt;

			public float QuietUntil;

			public void Clear()
			{
				State = HandHapticState.None;
				Now = 0f;
				NextPulseAt = 0f;
				CriticalSince = 0f;
				NextRefreshAt = 0f;
				QuietUntil = 0f;
			}
		}

		internal const float OnsetFraction = 0.5f;

		private const float AmplitudeExponent = 1.5f;

		private const float IntervalExponent = 1.1f;

		private const float AmplitudeMin = 0.04f;

		private const float AmplitudeMax = 0.55f;

		private const float IntervalMax = 0.85f;

		private const float IntervalMin = 0.06f;

		private const float PulseSecondsMin = 0.03f;

		private const float PulseSecondsMax = 0.07f;

		private const float TickFrequencyCalm = 170f;

		private const float TickFrequencyEmpty = 120f;

		private const float BothHandsScale = 0.8f;

		private const float CriticalEnterSeconds = 1f;

		private const float CriticalExitSeconds = 1.2f;

		private const float CriticalAmplitude = 0.62f;

		private const float CriticalFadeSeconds = 0.12f;

		private const float CriticalFrequency = 130f;

		private const float CriticalRefreshSeconds = 0.055f;

		private const float CriticalPulseSeconds = 0.08f;

		private const float ReleaseAmplitude = 0.9f;

		private const float ReleaseSeconds = 0.15f;

		private const float ReleaseFrequency = 140f;

		private const float PanicAmplitude = 0.75f;

		private const float PanicSeconds = 0.12f;

		private const float PanicFrequency = 150f;

		private const float PanicQuietSeconds = 0.3f;

		private readonly ClimbDetector _detector;

		private readonly HandSchedule _left = new HandSchedule();

		private readonly HandSchedule _right = new HandSchedule();

		internal HapticsController(ClimbDetector detector)
		{
			_detector = detector;
		}

		internal void Reset()
		{
			_left.Clear();
			_right.Clear();
		}

		internal void UpdateTwoHanded(float leftFraction, bool leftDraining, float leftSecondsLeft, float rightFraction, bool rightDraining, float rightSecondsLeft, float deltaTime)
		{
			RunWarnings(leftDraining, leftFraction, leftSecondsLeft, rightDraining, rightFraction, rightSecondsLeft, deltaTime);
		}

		private void RunWarnings(bool leftActive, float leftFraction, float leftSecondsLeft, bool rightActive, float rightFraction, float rightSecondsLeft, float deltaTime)
		{
			if (!Prefs.HapticsEnabled.Value)
			{
				_left.Clear();
				_right.Clear();
				return;
			}
			leftActive = leftActive && leftFraction < 0.5f && leftFraction > 0f;
			rightActive = rightActive && rightFraction < 0.5f && rightFraction > 0f;
			float scale = ((leftActive && rightActive) ? 0.8f : 1f);
			TickHand(_left, Player.LeftController, leftActive, leftFraction, leftSecondsLeft, scale, deltaTime);
			TickHand(_right, Player.RightController, rightActive, rightFraction, rightSecondsLeft, scale, deltaTime);
		}

		private static void TickHand(HandSchedule s, BaseController controller, bool active, float fraction, float secondsLeft, float scale, float deltaTime)
		{
			if (!active)
			{
				float quietUntil = Mathf.Max(0f, s.QuietUntil - s.Now - deltaTime);
				s.Clear();
				s.QuietUntil = quietUntil;
				return;
			}
			s.Now += deltaTime;
			float num = Mathf.Clamp01((0.5f - fraction) / 0.5f);
			float frequency = Mathf.Lerp(170f, 120f, num);
			if (secondsLeft <= ((s.State == HandHapticState.Critical) ? 1.2f : 1f))
			{
				if (s.State != HandHapticState.Critical)
				{
					s.State = HandHapticState.Critical;
					s.CriticalSince = s.Now;
					s.NextRefreshAt = s.Now;
				}
				if (s.Now < s.QuietUntil)
				{
					s.CriticalSince = s.Now;
				}
				else if (!(s.Now < s.NextRefreshAt))
				{
					float num2 = Mathf.Clamp01((s.Now - s.CriticalSince) / 0.12f);
					Pulse(controller, 0.08f, 130f, 0.62f * Mathf.Lerp(0.35f, 1f, num2) * scale);
					s.NextRefreshAt = s.Now + 0.055f;
				}
			}
			else
			{
				if (s.State != HandHapticState.Fatigue)
				{
					s.State = HandHapticState.Fatigue;
					s.NextPulseAt = s.Now;
				}
				if (!(s.Now < s.QuietUntil) && !(s.Now < s.NextPulseAt))
				{
					float amplitude = Mathf.Lerp(0.04f, 0.55f, Mathf.Pow(num, 1.5f)) * scale;
					float seconds = Mathf.Lerp(0.03f, 0.07f, num);
					float num3 = Mathf.Lerp(0.85f, 0.06f, Mathf.Pow(num, 1.1f));
					Pulse(controller, seconds, frequency, amplitude);
					s.NextPulseAt = s.Now + num3;
				}
			}
		}

		internal void PulseSlip(Handedness side, float scale)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Invalid comparison between Unknown and I4
			if (Prefs.HapticsEnabled.Value)
			{
				Pulse(((int)side == 1) ? Player.LeftController : Player.RightController, 0.1f, 95f, 0.8f * scale);
			}
		}

		internal void PulseReleaseHand(Handedness side)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Invalid comparison between Unknown and I4
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Invalid comparison between Unknown and I4
			(((int)side == 1) ? _left : _right).Clear();
			if (Prefs.HapticsEnabled.Value)
			{
				Pulse(((int)side == 1) ? Player.LeftController : Player.RightController, 0.15f, 140f, 0.9f);
			}
		}

		internal void PulsePanic(Hand hand)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Invalid comparison between Unknown and I4
			//IL_0040: 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_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Invalid comparison between Unknown and I4
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Invalid comparison between Unknown and I4
			if ((Object)(object)hand == (Object)null)
			{
				return;
			}
			HandSchedule obj = (((int)hand.handedness == 1) ? _left : _right);
			obj.QuietUntil = obj.Now + 0.3f;
			if (!Prefs.HapticsEnabled.Value)
			{
				return;
			}
			Handedness handedness = hand.handedness;
			BaseController controller;
			if ((int)handedness != 1)
			{
				if ((int)handedness != 2)
				{
					return;
				}
				controller = Player.RightController;
			}
			else
			{
				controller = Player.LeftController;
			}
			Pulse(controller, 0.12f, 150f, 0.75f);
		}

		private static void Pulse(BaseController controller, float seconds, float frequency, float amplitude)
		{
			try
			{
				if ((Object)(object)controller == (Object)null)
				{
					return;
				}
				Haptor haptor = controller.haptor;
				if (!((Object)(object)haptor == (Object)null) && haptor.hapticsAllowed)
				{
					amplitude = Mathf.Clamp01(amplitude * Mathf.Max(0f, Prefs.HapticStrength.Value));
					if (!(amplitude <= 0f))
					{
						haptor.SENDHAPTIC(0f, seconds, frequency, amplitude);
					}
				}
			}
			catch (Exception ex)
			{
				ClimbStaminaMod.LogDebug("Haptic pulse failed: " + ex.GetType().Name);
			}
		}
	}
	internal sealed class LedgeDetector
	{
		private enum LedgeKind
		{
			None,
			Side,
			Top
		}

		private sealed class HandLedgeState
		{
			internal bool OnLedge;

			internal LedgeKind Kind;

			internal float Holding;

			internal void Clear()
			{
				OnLedge = false;
				Kind = LedgeKind.None;
				Holding = 0f;
			}
		}

		private const float EvaluateHz = 15f;

		private const float EvaluateInterval = 1f / 15f;

		private const float ConfirmSeconds = 0.3f;

		private const float ReleaseSeconds = 0.15f;

		private const float MaxWallNormalY = 0.35f;

		private const float MinTopNormalY = 0.7f;

		private const float WallSearchDistance = 0.5f;

		private const float WallSearchRadius = 0.35f;

		private const float ProbeUp = 0.28f;

		private const float ProbeNear = 0.18f;

		private const float ProbeFar = 0.32f;

		private const float TopSearchDown = 0.6f;

		private const float TopMaxAboveHand = 0.45f;

		private const float TopMaxBelowHand = 0.2f;

		private const float TopUnderHandEnter = 0.35f;

		private const float TopUnderHandRemain = 0.5f;

		private const float EdgeRingEnter = 0.2f;

		private const float EdgeRingRemain = 0.32f;

		private const float DropDepth = 0.5f;

		private static readonly Vector3[] _ring = (Vector3[])(object)new Vector3[4]
		{
			new Vector3(1f, 0f, 0f),
			new Vector3(-1f, 0f, 0f),
			new Vector3(0f, 0f, 1f),
			new Vector3(0f, 0f, -1f)
		};

		private static readonly Collider[] _overlap = (Collider[])(object)new Collider[8];

		private readonly HandLedgeState _left = new HandLedgeState();

		private readonly HandLedgeState _right = new HandLedgeState();

		private float _sinceEvaluate;

		internal bool LeftOnLedge => _left.OnLedge;

		internal bool RightOnLedge => _right.OnLedge;

		internal void Reset()
		{
			_left.Clear();
			_right.Clear();
			_sinceEvaluate = 0f;
		}

		internal void Update(bool leftClimbing, bool rightClimbing, float deltaTime)
		{
			if (!Prefs.LedgeRestEnabled.Value || !PlayerTracker.IsValid)
			{
				Reset();
				return;
			}
			if (!leftClimbing)
			{
				ClearIfHeld(_left, "left");
			}
			if (!rightClimbing)
			{
				ClearIfHeld(_right, "right");
			}
			if (!leftClimbing && !rightClimbing)
			{
				return;
			}
			_sinceEvaluate += deltaTime;
			if (!(_sinceEvaluate < 1f / 15f))
			{
				float sinceEvaluate = _sinceEvaluate;
				_sinceEvaluate = 0f;
				if (leftClimbing)
				{
					Evaluate(_left, Player.LeftHand, "left", sinceEvaluate);
				}
				if (rightClimbing)
				{
					Evaluate(_right, Player.RightHand, "right", sinceEvaluate);
				}
			}
		}

		private static void ClearIfHeld(HandLedgeState state, string side)
		{
			if (state.OnLedge)
			{
				ClimbStaminaMod.LogDebug(side + ": ledge -> wall (released)");
			}
			state.Clear();
		}

		private static void Evaluate(HandLedgeState state, Hand hand, string side, float elapsed)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			LedgeKind ledgeKind = LedgeKind.None;
			try
			{
				if ((Object)(object)hand != (Object)null && PlayerTracker.IsLocalHand(hand))
				{
					ledgeKind = Classify(((Component)hand).transform.position, state.OnLedge);
				}
			}
			catch (Exception ex)
			{
				ClimbStaminaMod.LogDebug($"Ledge probe failed ({ex.GetType().Name}); treating {side} as wall");
				ledgeKind = LedgeKind.None;
			}
			bool flag = ledgeKind != LedgeKind.None;
			if (flag == state.OnLedge)
			{
				state.Holding = 0f;
				if (flag && ledgeKind != state.Kind)
				{
					ClimbStaminaMod.LogDebug(side + " ledge remain: " + ledgeKind.ToString().ToLowerInvariant());
					state.Kind = ledgeKind;
				}
				return;
			}
			state.Holding += elapsed;
			float num = (flag ? 0.3f : 0.15f);
			if (!(state.Holding < num))
			{
				state.OnLedge = flag;
				state.Holding = 0f;
				if (flag)
				{
					state.Kind = ledgeKind;
					ClimbStaminaMod.LogDebug(side + " ledge enter: " + ledgeKind.ToString().ToLowerInvariant());
				}
				else
				{
					state.Kind = LedgeKind.None;
					ClimbStaminaMod.LogDebug(side + " ledge exit");
				}
			}
		}

		private static LedgeKind Classify(Vector3 handPos, bool alreadyResting)
		{
			//IL_0000: 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)
			if (IsSideLedge(handPos))
			{
				return LedgeKind.Side;
			}
			if (IsTopLedge(handPos, alreadyResting))
			{
				return LedgeKind.Top;
			}
			return LedgeKind.None;
		}

		private static bool IsTopLedge(Vector3 handPos, bool alreadyResting)
		{
			//IL_0010: 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_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: 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_009e: 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_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: 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_00c2: Unknown result type (might be due to invalid IL or missing references)
			float num = (alreadyResting ? 0.5f : 0.35f);
			RaycastHit val = default(RaycastHit);
			if (!Physics.Raycast(handPos + Vector3.up * 0.05f, Vector3.down, ref val, num + 0.05f, -5, (QueryTriggerInteraction)1))
			{
				return false;
			}
			if ((Object)(object)((RaycastHit)(ref val)).collider == (Object)null || (Object)(object)((RaycastHit)(ref val)).collider.attachedRigidbody != (Object)null)
			{
				return false;
			}
			if (((RaycastHit)(ref val)).normal.y < 0.7f)
			{
				return false;
			}
			float num2 = (alreadyResting ? 0.32f : 0.2f);
			Vector3 point = ((RaycastHit)(ref val)).point;
			RaycastHit val2 = default(RaycastHit);
			for (int i = 0; i < _ring.Length; i++)
			{
				if (!Physics.Raycast(point + _ring[i] * num2 + Vector3.up * 0.05f, Vector3.down, ref val2, 0.5f, -5, (QueryTriggerInteraction)1))
				{
					return true;
				}
			}
			return false;
		}

		private static bool IsSideLedge(Vector3 handPos)
		{
			//IL_0000: 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_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: 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_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: 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_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_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 (!TryFindWall(handPos, out var outward))
			{
				return false;
			}
			Vector3 val = -outward;
			if (Physics.Raycast(handPos + Vector3.up * 0.28f + outward * 0.05f, val, 0.3f, -5, (QueryTriggerInteraction)1))
			{
				return false;
			}
			if (HasTopSurface(handPos, val, 0.18f))
			{
				return HasTopSurface(handPos, val, 0.32f);
			}
			return false;
		}

		private static bool HasTopSurface(Vector3 handPos, Vector3 inward, float depth)
		{
			//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_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_0015: 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_001c: 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_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_007f: Unknown result type (might be due to invalid IL or missing references)
			RaycastHit val = default(RaycastHit);
			if (!Physics.Raycast(handPos + Vector3.up * 0.28f + inward * depth, Vector3.down, ref val, 0.6f, -5, (QueryTriggerInteraction)1))
			{
				return false;
			}
			if ((Object)(object)((RaycastHit)(ref val)).collider == (Object)null || (Object)(object)((RaycastHit)(ref val)).collider.attachedRigidbody != (Object)null)
			{
				return false;
			}
			if (((RaycastHit)(ref val)).normal.y < 0.7f)
			{
				return false;
			}
			float num = ((RaycastHit)(ref val)).point.y - handPos.y;
			if (num <= 0.45f)
			{
				return num >= -0.2f;
			}
			return false;
		}

		private static bool TryFindWall(Vector3 handPos, out Vector3 outward)
		{
			//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_0010: 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_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_0062: 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_0056: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_006c: 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_006e: Unknown result type (might be due to invalid IL or missing references)
			outward = Vector3.zero;
			Vector3 outward2 = Vector3.zero;
			PhysicsRig physicsRig = Player.PhysicsRig;
			if ((Object)(object)physicsRig != (Object)null)
			{
				Vector3 climbingNormal = physicsRig.climbingNormal;
				climbingNormal.y = 0f;
				if (((Vector3)(ref climbingNormal)).sqrMagnitude > 0.04f)
				{
					outward2 = ((Vector3)(ref climbingNormal)).normalized;
				}
			}
			if (outward2 == Vector3.zero && !TryNearestSurface(handPos, out outward2))
			{
				return false;
			}
			if (!ConfirmWall(handPos, outward2, out outward))
			{
				return ConfirmWall(handPos, -outward2, out outward);
			}
			return true;
		}

		private static bool ConfirmWall(Vector3 handPos, Vector3 candidate, out Vector3 outward)
		{
			//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_000d: 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_0051: Unknown result type (might be due to invalid IL or missing references)
			//IL_0052: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: 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)
			outward = Vector3.zero;
			RaycastHit val = default(RaycastHit);
			if (!Physics.Raycast(handPos, -candidate, ref val, 0.5f, -5, (QueryTriggerInteraction)1))
			{
				return false;
			}
			if ((Object)(object)((RaycastHit)(ref val)).collider == (Object)null || (Object)(object)((RaycastHit)(ref val)).collider.attachedRigidbody != (Object)null)
			{
				return false;
			}
			Vector3 normal = ((RaycastHit)(ref val)).normal;
			if (Mathf.Abs(normal.y) > 0.35f)
			{
				return false;
			}
			normal.y = 0f;
			if (((Vector3)(ref normal)).sqrMagnitude < 0.04f)
			{
				return false;
			}
			outward = ((Vector3)(ref normal)).normalized;
			return true;
		}

		private static bool TryNearestSurface(Vector3 handPos, out Vector3 outward)
		{
			//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_009c: 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_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: 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_0089: 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)
			outward = Vector3.zero;
			int num = Physics.OverlapSphereNonAlloc(handPos, 0.35f, Il2CppReferenceArray<Collider>.op_Implicit(_overlap), -5, (QueryTriggerInteraction)1);
			float num2 = float.MaxValue;
			for (int i = 0; i < num; i++)
			{
				Collider val = _overlap[i];
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.attachedRigidbody != (Object)null))
				{
					Vector3 val2 = val.ClosestPoint(handPos);
					Vector3 val3 = handPos - val2;
					val3.y = 0f;
					float sqrMagnitude = ((Vector3)(ref val3)).sqrMagnitude;
					if (!(sqrMagnitude < 0.0001f) && !(sqrMagnitude >= num2))
					{
						num2 = sqrMagnitude;
						outward = ((Vector3)(ref val3)).normalized;
					}
				}
			}
			return outward != Vector3.zero;
		}
	}
	internal sealed class PanicGrabController
	{
		private const float PeakHoldSeconds = 0.2f;

		private const float CooldownSeconds = 0.75f;

		private readonly ClimbDetector _detector;

		private readonly StaminaController _stamina;

		private readonly HapticsController _haptics;

		private float _peakFallSpeed;

		private float _peakAge;

		private float _cooldown;

		internal PanicGrabController(ClimbDetector detector, StaminaController stamina, HapticsController haptics)
		{
			_detector = detector;
			_stamina = stamina;
			_haptics = haptics;
		}

		internal void Reset()
		{
			_peakFallSpeed = 0f;
			_peakAge = 0f;
			_cooldown = 0f;
		}

		internal void Update(float deltaTime)
		{
			if (_cooldown > 0f)
			{
				_cooldown -= deltaTime;
			}
			float verticalVelocity = PlayerTracker.VerticalVelocity;
			if (PlayerTracker.IsStanding)
			{
				_peakFallSpeed = verticalVelocity;
				_peakAge = 0f;
				return;
			}
			if (verticalVelocity <= _peakFallSpeed)
			{
				_peakFallSpeed = verticalVelocity;
				_peakAge = 0f;
				return;
			}
			_peakAge += deltaTime;
			if (_peakAge >= 0.2f)
			{
				_peakFallSpeed = verticalVelocity;
				_peakAge = 0f;
			}
		}

		internal void OnClimbAttached(Hand hand)
		{
			if (Prefs.PanicGrabEnabled.Value && !(_cooldown > 0f) && _detector.ClimbingHandCount == 1 && !PlayerTracker.IsStanding)
			{
				float num = Mathf.Max(0.5f, Prefs.PanicFallSpeed.Value);
				if (!(_peakFallSpeed > 0f - num))
				{
					_cooldown = 0.75f;
					ClimbStaminaMod.LogDebug($"Panic grab at {_peakFallSpeed:0.0} m/s");
					_peakFallSpeed = 0f;
					_peakAge = 0f;
					_haptics.PulsePanic(hand);
					ClimbStaminaMod.FlashGauges();
					ClimbStaminaMod.RoutePanicCost(hand);
				}
			}
		}
	}
	internal static class PlayerTracker
	{
		private static float _avatarDrainMultiplier = 1f;

		private static float _maxReach = 0.75f;

		private const float MinGroundNormalUp = 0.55f;

		internal static bool IsValid
		{
			get
			{
				if (HelperMethods.IsLoading())
				{
					return false;
				}
				if ((Object)(object)Player.RigManager != (Object)null)
				{
					return (Object)(object)Player.PhysicsRig != (Object)null;
				}
				return false;
			}
		}

		internal static bool IsStanding
		{
			get
			{
				//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_0018: Unknown result type (might be due to invalid IL or missing references)
				//IL_001a: Invalid comparison between Unknown and I4
				//IL_001c: Unknown result type (might be due to invalid IL or missing references)
				//IL_001e: Invalid comparison between Unknown and I4
				//IL_0020: Unknown result type (might be due to invalid IL or missing references)
				//IL_0022: Invalid comparison between Unknown and I4
				//IL_0043: Unknown result type (might be due to invalid IL or missing references)
				PhysicsRig physicsRig = Player.PhysicsRig;
				if ((Object)(object)physicsRig == (Object)null)
				{
					return true;
				}
				StepState stepState = physicsRig._stepState;
				if ((int)stepState == 3 || (int)stepState == 4 || (int)stepState == 5)
				{
					return false;
				}
				PhysGrounder physG = physicsRig.physG;
				if ((Object)(object)physG == (Object)null)
				{
					return true;
				}
				if (!physG.isGrounded)
				{
					return false;
				}
				return physG.groundNormal.y >= 0.55f;
			}
		}

		internal static float VerticalVelocity
		{
			get
			{
				//IL_0042: Unknown result type (might be due to invalid IL or missing references)
				PhysicsRig physicsRig = Player.PhysicsRig;
				if ((Object)(object)physicsRig == (Object)null)
				{
					return 0f;
				}
				PhysTorso torso = physicsRig.torso;
				if ((Object)(object)torso == (Object)null)
				{
					return 0f;
				}
				Rigidbody rbPelvis = torso.rbPelvis;
				if ((Object)(object)rbPelvis == (Object)null)
				{
					return 0f;
				}
				return rbPelvis.velocity.y;
			}
		}

		internal static float AvatarDrainMultiplier => _avatarDrainMultiplier;

		internal static float MaxReach => _maxReach;

		internal static bool IsLocalHand(Hand hand)
		{
			if ((Object)(object)hand == (Object)null)
			{
				return false;
			}
			RigManager rigManager = Player.RigManager;
			if ((Object)(object)rigManager != (Object)null)
			{
				return (Object)(object)hand.manager == (Object)(object)rigManager;
			}
			return false;
		}

		internal static string DescribeGroundState()
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: 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)
			try
			{
				PhysicsRig physicsRig = Player.PhysicsRig;
				if ((Object)(object)physicsRig == (Object)null)
				{
					return "physicsRig=null";
				}
				PhysGrounder physG = physicsRig.physG;
				if (!((Object)(object)physG == (Object)null))
				{
					Collider groundedCollider = physG._groundedCollider;
					return $"step={physicsRig._stepState}, isGrounded={physG.isGrounded}, normalY={physG.groundNormal.y:0.00}, groundCollider={(((Object)(object)groundedCollider != (Object)null) ? ((Object)groundedCollider).name : "none")}, footSupported={physicsRig.footSupported:0.00}";
				}
				return $"step={physicsRig._stepState}, grounder=null";
			}
			catch (Exception ex)
			{
				return "unavailable (" + ex.GetType().Name + ")";
			}
		}

		internal static bool TryGetChestPosition(out Vector3 position)
		{
			//IL_0001: 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)
			position = default(Vector3);
			PhysicsRig physicsRig = Player.PhysicsRig;
			if ((Object)(object)physicsRig == (Object)null)
			{
				return false;
			}
			PhysTorso torso = physicsRig.torso;
			if ((Object)(object)torso == (Object)null)
			{
				return false;
			}
			Rigidbody val = torso.rbChest;
			if ((Object)(object)val == (Object)null)
			{
				val = torso.rbPelvis;
			}
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			position = val.position;
			return true;
		}

		internal static void RefreshAvatarMultiplier()
		{
			RefreshMaxReach();
			_avatarDrainMultiplier = 1f;
			if (!Prefs.AvatarStrengthScaling.Value)
			{
				return;
			}
			RigManager rigManager = Player.RigManager;
			if ((Object)(object)rigManager == (Object)null)
			{
				return;
			}
			Avatar avatar = rigManager.avatar;
			if (!((Object)(object)avatar == (Object)null))
			{
				float strengthUpper = avatar._strengthUpper;
				float num = Mathf.Max(0.01f, Prefs.AvatarReferenceStrength.Value);
				if (float.IsNaN(strengthUpper) || strengthUpper <= 0f)
				{
					ClimbStaminaMod.LogDebug("Avatar strengthUpper unavailable; multiplier stays 1.0");
					return;
				}
				_avatarDrainMultiplier = Mathf.Clamp(num / strengthUpper, 0.7f, 1.3f);
				ClimbStaminaMod.LogDebug($"Avatar strengthUpper={strengthUpper:0.###} reference={num:0.###} drainMult={_avatarDrainMultiplier:0.###}");
			}
		}

		private static void RefreshMaxReach()
		{
			_maxReach = 0.75f;
			try
			{
				RigManager rigManager = Player.RigManager;
				Avatar val = (((Object)(object)rigManager != (Object)null) ? rigManager.avatar : null);
				if (!((Object)(object)val == (Object)null))
				{
					float height = val._height;
					if (!float.IsNaN(height) && !(height <= 0f))
					{
						_maxReach = Mathf.Clamp(height * 0.45f, 0.45f, 1.3f);
						ClimbStaminaMod.LogDebug($"Avatar reach reference: height={height:0.00} maxReach={_maxReach:0.00}");
					}
				}
			}
			catch
			{
			}
		}
	}
	internal static class Prefs
	{
		internal static MelonPreferences_Category Category;

		internal static MelonPreferences_Entry<bool> Enabled;

		internal static MelonPreferences_Entry<float> MaxStamina;

		internal static MelonPreferences_Entry<float> OneHandDrainRate;

		internal static MelonPreferences_Entry<float> TwoHandDrainRate;

		internal static MelonPreferences_Entry<float> AscendingMultiplier;

		internal static MelonPreferences_Entry<float> GroundedRegenRate;

		internal static MelonPreferences_Entry<float> AirborneRegenRate;

		internal static MelonPreferences_Entry<float> RecoveryDelay;

		internal static MelonPreferences_Entry<float> MinRegrabStamina;

		internal static MelonPreferences_Entry<bool> LedgeRestEnabled;

		internal static MelonPreferences_Entry<float> TwoHandLedgeDrainRate;

		internal static MelonPreferences_Entry<bool> AvatarStrengthScaling;

		internal static MelonPreferences_Entry<float> AvatarReferenceStrength;

		internal static MelonPreferences_Entry<bool> CrumblingGrip;

		internal static MelonPreferences_Entry<bool> HandGauges;

		internal static MelonPreferences_Entry<bool> ShowPercent;

		internal static MelonPreferences_Entry<float> HandGaugePosX;

		internal static MelonPreferences_Entry<float> HandGaugePosY;

		internal static MelonPreferences_Entry<float> HandGaugePosZ;

		internal static MelonPreferences_Entry<float> HandGaugeRotX;

		internal static MelonPreferences_Entry<float> HandGaugeRotY;

		internal static MelonPreferences_Entry<float> HandGaugeRotZ;

		internal static MelonPreferences_Entry<float> HandGaugeScale;

		internal static MelonPreferences_Entry<bool> HandGaugeMirror;

		internal static MelonPreferences_Entry<bool> HapticsEnabled;

		internal static MelonPreferences_Entry<float> HapticStrength;

		internal static MelonPreferences_Entry<bool> PanicGrabEnabled;

		internal static MelonPreferences_Entry<float> PanicFallSpeed;

		internal static MelonPreferences_Entry<float> PanicStaminaCost;

		internal static MelonPreferences_Entry<bool> DebugLogging;

		internal static void Setup()
		{
			Category = MelonPreferences.CreateCategory("ClimbStamina", "Climb Stamina");
			Enabled = Category.CreateEntry<bool>("Enabled", true, "Enabled", "Master switch for the climbing stamina system.", false, false, (ValueValidator)null, (string)null);
			MaxStamina = Category.CreateEntry<float>("MaxStamina", 100f, "Max Stamina", "Total stamina pool.", false, false, (ValueValidator)null, (string)null);
			OneHandDrainRate = Category.CreateEntry<float>("OneHandDrainRate", 8f, "One-Hand Drain Rate", "Stamina drained per second while hanging from one hand.", false, false, (ValueValidator)null, (string)null);
			TwoHandDrainRate = Category.CreateEntry<float>("TwoHandDrainRate", 4f, "Two-Hand Drain Rate", "Stamina drained per second while hanging from two hands.", false, false, (ValueValidator)null, (string)null);
			AscendingMultiplier = Category.CreateEntry<float>("AscendingMultiplier", 1.3f, "Ascending Multiplier", "Extra drain while climbing upward, scaled by how fast you rise: this multiplier at a brisk pace, up to three times its extra at a full sprint.", false, false, (ValueValidator)null, (string)null);
			GroundedRegenRate = Category.CreateEntry<float>("GroundedRegenRate", 20f, "Grounded Regen Rate", "Stamina per second while standing on the ground. Regen eases in - slow at first, picking up pace - so a full refill runs slightly past what the rate alone suggests.", false, false, (ValueValidator)null, (string)null);
			AirborneRegenRate = Category.CreateEntry<float>("AirborneRegenRate", 16.7f, "Airborne Regen Rate", "Average stamina per second while airborne (not climbing). 16.7 refills an empty bar in 6 seconds, easing in: slow at first, picking up pace.", false, false, (ValueValidator)null, (string)null);
			RecoveryDelay = Category.CreateEntry<float>("RecoveryDelay", 1f, "Recovery Delay", "Seconds after climbing stops before stamina starts regenerating.", false, false, (ValueValidator)null, (string)null);
			MinRegrabStamina = Category.CreateEntry<float>("MinRegrabStamina", 100f, "Regrab Stamina %", "After exhaustion, climbing stays blocked until the bar refills to this percent.", false, false, (ValueValidator)null, (string)null);
			LedgeRestEnabled = Category.CreateEntry<bool>("LedgeRestEnabled", true, "Ledge Rest", "Hanging from a real ledge with BOTH hands drains stamina much more slowly than a bare wall. It never regenerates while you hang.", false, false, (ValueValidator)null, (string)null);
			TwoHandLedgeDrainRate = Category.CreateEntry<float>("TwoHandLedgeDrainRate", 0.575f, "Two-Hand Ledge Drain", "Stamina per second while hanging from a ledge with both hands. One hand always uses the normal one-hand rate.", false, false, (ValueValidator)null, (string)null);
			AvatarStrengthScaling = Category.CreateEntry<bool>("AvatarStrengthScaling", false, "Avatar Strength Scaling", "Scale drain by the avatar's upper-body strength stat (clamped 0.70x-1.30x).", false, false, (ValueValidator)null, (string)null);
			AvatarReferenceStrength = Category.CreateEntry<float>("AvatarReferenceStrength", 1f, "Avatar Reference Strength", "strengthUpper value treated as the 1.0x baseline. Enable Debug Logging to see your avatars' values.", false, false, (ValueValidator)null, (string)null);
			CrumblingGrip = Category.CreateEntry<bool>("CrumblingGrip", true, "Crumbling Grip", "In the last seconds of a hold (under 10% stamina, or about 3 seconds from failure at the current drain) your grip starts to physically fail: brief centimetre slips with a sharp thump, quicker and deeper as the bar empties. Feedback-only warnings start at 50%.", false, false, (ValueValidator)null, (string)null);
			HandGauges = Category.CreateEntry<bool>("HandGauges", true, "Hand Gauges", "The holographic stamina arcs on each wrist. Off hides the HUD entirely; stamina still runs.", false, false, (ValueValidator)null, (string)null);
			ShowPercent = Category.CreateEntry<bool>("ShowGaugePercent", true, "Show Percentage", "The numeric stamina percent on each wrist gauge. Off keeps the arcs, chips and labels; only the big number goes.", false, false, (ValueValidator)null, (string)null);
			HandGaugePosX = Category.CreateEntry<float>("HandGaugePosX", -0.04f, "Gauge Offset X", "Local X offset from the hand, metres. Mirrored on the right hand.", false, false, (ValueValidator)null, (string)null);
			HandGaugePosY = Category.CreateEntry<float>("HandGaugePosY", 0.005f, "Gauge Offset Y", "Local Y offset from the hand, metres.", false, false, (ValueValidator)null, (string)null);
			HandGaugePosZ = Category.CreateEntry<float>("HandGaugePosZ", 0.05f, "Gauge Offset Z", "Local Z offset from the hand, metres (negative = toward the wrist).", false, false, (ValueValidator)null, (string)null);
			HandGaugeRotX = Category.CreateEntry<float>("HandGaugeRotX", 0f, "Gauge Rotation X", "Local pitch in degrees.", false, false, (ValueValidator)null, (string)null);
			HandGaugeRotY = Category.CreateEntry<float>("HandGaugeRotY", 85f, "Gauge Rotation Y", "Local yaw in degrees. Mirrored on the right hand.", false, false, (ValueValidator)null, (string)null);
			HandGaugeRotZ = Category.CreateEntry<float>("HandGaugeRotZ", 85f, "Gauge Rotation Z", "Local roll in degrees. Mirrored on the right hand.", false, false, (ValueValidator)null, (string)null);
			HandGaugeScale = Category.CreateEntry<float>("HandGaugeScale", 1.8f, "Gauge Scale", "Size multiplier; 1.0 is about 8.5 cm across.", false, false, (ValueValidator)null, (string)null);
			HandGaugeMirror = Category.CreateEntry<bool>("HandGaugeMirror", true, "Gauge Mirror Right", "Mirror the pose across X for the right hand.", false, false, (ValueValidator)null, (string)null);
			HapticsEnabled = Category.CreateEntry<bool>("HapticsEnabled", true, "Haptic Feedback", "Pulse the gripping hands' controllers as stamina runs low.", false, false, (ValueValidator)null, (string)null);
			HapticStrength = Category.CreateEntry<float>("HapticStrength", 1f, "Haptic Strength", "Multiplier on haptic pulse strength (0 = silent, 1 = default).", false, false, (ValueValidator)null, (string)null);
			PanicGrabEnabled = Category.CreateEntry<bool>("PanicGrabEnabled", true, "Panic Grab", "Catching a climbable surface mid-fall costs stamina and briefly drains faster.", false, false, (ValueValidator)null, (string)null);
			PanicFallSpeed = Category.CreateEntry<float>("PanicFallSpeed", 4f, "Panic Fall Speed", "Downward speed (m/s) a fall must reach for a grab to count as a panic grab.", false, false, (ValueValidator)null, (string)null);
			PanicStaminaCost = Category.CreateEntry<float>("PanicStaminaCost", 12f, "Panic Stamina Cost %", "Stamina lost instantly on a panic grab, as a percent of max.", false, false, (ValueValidator)null, (string)null);
			DebugLogging = Category.CreateEntry<bool>("DebugLogging", false, "Debug Logging", "Verbose state logging for troubleshooting.", false, false, (ValueValidator)null, (string)null);
		}

		internal static void Save()
		{
			MelonPreferences.Save();
		}
	}
	internal static class RegenCurve
	{
		private const float RampStart = 0.3f;

		private const float RampPeak = 1.7f;

		private const float RampSeconds = 6f;

		internal static float Factor(float timeRegenerating)
		{
			float num = Mathf.Clamp01(timeRegenerating / 6f);
			return Mathf.Lerp(0.3f, 1.7f, num);
		}
	}
	internal static class SettingsMenu
	{
		internal static void Setup()
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: 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_009f: 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_0112: Unknown result type (might be due to invalid IL or missing references)
			Page obj = Page.Root.CreatePage("Climb Stamina", new Color(0.35f, 0.75f, 1f), 0, true);
			obj.CreateBool("Enabled", Color.white, Prefs.Enabled.Value, (Action<bool>)delegate(bool v)
			{
				Prefs.Enabled.Value = v;
				Prefs.Save();
			});
			obj.CreateBool("Crumbling Grip", Color.white, Prefs.CrumblingGrip.Value, (Action<bool>)delegate(bool v)
			{
				Prefs.CrumblingGrip.Value = v;
				Prefs.Save();
			});
			obj.CreateBool("Ledge Rest", Color.white, Prefs.LedgeRestEnabled.Value, (Action<bool>)delegate(bool v)
			{
				Prefs.LedgeRestEnabled.Value = v;
				Prefs.Save();
			});
			obj.CreateBool("Hand Gauges", Color.white, Prefs.HandGauges.Value, (Action<bool>)delegate(bool v)
			{
				Prefs.HandGauges.Value = v;
				Prefs.Save();
			});
			obj.CreateBool("Show Percentage", Color.white, Prefs.ShowPercent.Value, (Action<bool>)delegate(bool v)
			{
				Prefs.ShowPercent.Value = v;
				Prefs.Save();
			});
		}
	}
	internal enum DrainMode
	{
		None,
		WallOne,
		WallTwo,
		Mixed,
		LedgeTwo
	}
	internal enum StaminaState
	{
		READY,
		CLIMBING_ONE_HAND,
		CLIMBING_TWO_HANDS,
		RECOVERY_DELAY,
		RECOVERING,
		EXHAUSTED
	}
	internal sealed class StaminaController
	{
		private const float AscendSmoothingRate = 8f;

		internal const float LowStaminaFraction = 0.25f;

		private const float PanicDrainWindowSeconds = 1f;

		private const float PanicDrainMultiplier = 1.5f;

		private readonly ClimbDetector _detector;

		private readonly GripController _grips;

		private readonly LedgeDetector _ledges;

		private float _stamina;

		private float _timeSinceDrain = float.MaxValue;

		private float _smoothedVerticalVelocity;

		private bool _wasSupporting;

		private float _panicDrainRemaining;

		private DrainMode _mode;

		internal StaminaState State { get; private set; }

		internal float Stamina => _stamina;

		internal float MaxStamina => Mathf.Max(1f, Prefs.MaxStamina.Value);

		internal float Fraction => Mathf.Clamp01(_stamina / MaxStamina);

		internal bool LeftHandClimbing => _detector.LeftClimbing;

		internal bool RightHandClimbing => _detector.RightClimbing;

		internal bool IsExhausted => State == StaminaState.EXHAUSTED;

		internal float LastDrainRate { get; private set; }

		internal float SecondsToExhaustion
		{
			get
			{
				if ((State != StaminaState.CLIMBING_ONE_HAND && State != StaminaState.CLIMBING_TWO_HANDS) || LastDrainRate <= 0.001f)
				{
					return float.PositiveInfinity;
				}
				return Mathf.Max(0f, _stamina) / LastDrainRate;
			}
		}

		internal void MirrorPool(float value)
		{
			_stamina = Mathf.Clamp(value, 0f, MaxStamina);
		}

		internal StaminaController(ClimbDetector detector, GripController grips, LedgeDetector ledges)
		{
			_detector = detector;
			_grips = grips;
			_ledges = ledges;
			_stamina = MaxStamina;
		}

		internal void ResetFull()
		{
			_stamina = MaxStamina;
			_timeSinceDrain = float.MaxValue;
			_smoothedVerticalVelocity = 0f;
			_wasSupporting = false;
			_panicDrainRemaining = 0f;
			_mode = DrainMode.None;
			State = StaminaState.READY;
			ClimbStaminaMod.ClimbBlocked = false;
		}

		internal void Update(float deltaTime)
		{
			if (!PlayerTracker.IsValid)
			{
				_detector.Clear();
				_grips.Clear();
				_ledges.Reset();
				_smoothedVerticalVelocity = 0f;
				return;
			}
			_grips.ProcessQueue();
			_detector.Validate();
			_ledges.Update(_detector.LeftClimbing, _detector.RightClimbing, deltaTime);
			float maxStamina = MaxStamina;
			_stamina = Mathf.Min(_stamina, maxStamina);
			int climbingHandCount = _detector.ClimbingHandCount;
			bool isStanding = PlayerTracker.IsStanding;
			bool flag = climbingHandCount > 0 && !isStanding;
			if (flag != _wasSupporting)
			{
				if (Prefs.DebugLogging.Value && climbingHandCount > 0)
				{
					ClimbStaminaMod.LogDebug($"support {(flag ? "gained" : "lost")}: hands={climbingHandCount}, {PlayerTracker.DescribeGroundState()}");
				}
				_wasSupporting = flag;
			}
			UpdateAscendSmoothing(deltaTime, flag);
			if (_timeSinceDrain < float.MaxValue)
			{
				_timeSinceDrain += deltaTime;
			}
			if (_panicDrainRemaining > 0f)
			{
				_panicDrainRemaining -= deltaTime;
			}
			if (State == StaminaState.EXHAUSTED)
			{
				GripController.SweepClimbingHands();
				ClimbAnythingCompatibility.ForceReleaseAll();
				_detector.Clear();
				Regenerate(deltaTime, isStanding);
				float num = maxStamina * Mathf.Clamp01(Prefs.MinRegrabStamina.Value / 100f);
				if (_timeSinceDrain >= Prefs.RecoveryDelay.Value && _stamina >= num)
				{
					State = StaminaState.READY;
					ClimbStaminaMod.ClimbBlocked = false;
					ClimbStaminaMod.LogDebug($"Recovered from exhaustion at stamina={_stamina:0.0}");
				}
			}
			else if (flag)
			{
				State = ((climbingHandCount < 2) ? StaminaState.CLIMBING_ONE_HAND : StaminaState.CLIMBING_TWO_HANDS);
				bool value = Prefs.LedgeRestEnabled.Value;
				bool flag2 = value && _detector.LeftClimbing && _ledges.LeftOnLedge;
				bool flag3 = value && _detector.RightClimbing && _ledges.RightOnLedge;
				int num2 = (flag2 ? 1 : 0) + (flag3 ? 1 : 0);
				DrainMode drainMode;
				float num3;
				if (climbingHandCount >= 2 && num2 >= 2)
				{
					num3 = Prefs.TwoHandLedgeDrainRate.Value;
					drainMode = DrainMode.LedgeTwo;
				}
				else
				{
					num3 = ((climbingHandCount >= 2) ? Prefs.TwoHandDrainRate.Value : Prefs.OneHandDrainRate.Value);
					drainMode = ((climbingHandCount < 2) ? DrainMode.WallOne : ((num2 == 1) ? DrainMode.Mixed : DrainMode.WallTwo));
				}
				if (drainMode != _mode)
				{
					_mode = drainMode;
					ClimbStaminaMod.LogDebug($"drain mode: {drainMode} (climbing {_detector.LeftClimbing}/{_detector.RightClimbing}, ledge {flag2}/{flag3})");
				}
				num3 *= AscendCurve.Factor(_smoothedVerticalVelocity);
				num3 *= PlayerTracker.AvatarDrainMultiplier;
				if (_panicDrainRemaining > 0f)
				{
					num3 *= 1.5f;
				}
				LastDrainRate = Mathf.Max(0f, num3);
				_stamina -= LastDrainRate * deltaTime;
				_timeSinceDrain = 0f;
				if (_stamina <= 0f)
				{
					_stamina = 0f;
					Exhaust(deferRelease: false);
				}
			}
			else if (_stamina >= maxStamina)
			{
				State = StaminaState.READY;
			}
			else if (_timeSinceDrain < Prefs.RecoveryDelay.Value)
			{
				State = StaminaState.RECOVERY_DELAY;
			}
			else
			{
				State = StaminaState.RECOVERING;
				Regenerate(deltaTime, isStanding);
				if (_stamina >= maxStamina)
				{
					_stamina = maxStamina;
					State = StaminaState.READY;
				}
			}
		}

		internal void