Decompiled source of ClimbStamina v2.0.1
Mods/ClimbStamina.dll
Decompiled a month ago
The result has been truncated due to the large size, download it to view full contents!
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