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 BoneLib.Notifications; using ClimbStamina; using ClimbStamina.Compatibility; using HarmonyLib; using Il2CppInterop.Runtime; 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", "1.3.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.3.0.0")] [assembly: AssemblyInformationalVersion("1.3.0+c417cbe30ed54091dc052cf07de631265199e42b")] [assembly: AssemblyProduct("ClimbStamina")] [assembly: AssemblyTitle("ClimbStamina")] [assembly: AssemblyVersion("1.3.0.0")] namespace ClimbStamina { 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 OnBlockedClimbAttach; internal Action OnClimbAttached; internal Func CanStartClimb = () => true; internal int ClimbingHandCount => (_left.IsClimbing ? 1 : 0) + (_right.IsClimbing ? 1 : 0); internal bool LeftClimbing => _left.IsClimbing; internal bool RightClimbing => _right.IsClimbing; internal void Subscribe() { Hooking.OnGripAttached += OnGripAttached; Hooking.OnGripDetached += OnGripDetached; } internal void Unsubscribe() { Hooking.OnGripAttached -= OnGripAttached; Hooking.OnGripDetached -= OnGripDetached; } internal static bool IsClimbingGrip(Grip grip) { if ((Object)(object)grip == (Object)null) { return false; } if ((Object)(object)((Il2CppObjectBase)grip).TryCast() != (Object)null) { return false; } if ((Object)(object)((Il2CppObjectBase)grip).TryCast() != (Object)null) { return true; } return ((HandReciever)grip).IsStatic; } private void OnGripAttached(Grip grip, Hand hand) { if (!PlayerTracker.IsLocalHand(hand) || !IsClimbingGrip(grip)) { return; } if (!CanStartClimb()) { 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); } 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(); if (!((Object)(object)val == (Object)null) && IsClimbingGrip(val)) { slot.Hand = hand; slot.Grip = val; } } } internal void ForEachClimbingHand(Action action) { if (_left.IsClimbing) { action(_left.Hand); } if (_right.IsClimbing) { action(_right.Hand); } } internal void Clear() { _left.Clear(); _right.Clear(); } 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; private static Instance _logger; private static float _lastBlockedNotifyStatic = -999f; private ClimbDetector _detector; private GripController _grips; private StaminaController _stamina; private StaminaHud _hud; private HapticsController _haptics; private PanicGrabController _panic; private bool _wasEnabled = true; private float _debugLogTimer; private Action _onLevelLoaded; private Action _onLevelUnloaded; private Action _onAvatarSwitched; private Action _onPlayerDeath; private Action _onPlayerResurrected; public override void OnInitializeMelon() { _logger = ((MelonBase)this).LoggerInstance; Prefs.Setup(); SettingsMenu.Setup(); _detector = new ClimbDetector(); _grips = new GripController(); _stamina = new StaminaController(_detector, _grips); _hud = new StaminaHud(); _haptics = new HapticsController(_detector); _panic = new PanicGrabController(_detector, _stamina, _haptics, _hud); _detector.OnBlockedClimbAttach = OnBlockedClimbAttach; _detector.OnClimbAttached = _panic.OnClimbAttached; _stamina.OnExhausted = OnExhausted; _stamina.OnLowStamina = OnLowStamina; _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(); } }; _onPlayerResurrected = delegate(RigManager rig) { if (IsLocalRig(rig)) { _stamina.ResetFull(); } }; Hooking.OnLevelLoaded += _onLevelLoaded; Hooking.OnLevelUnloaded += _onLevelUnloaded; Hooking.OnSwitchAvatarPostfix += _onAvatarSwitched; Hooking.OnPlayerDeath += _onPlayerDeath; Hooking.OnPlayerResurrected += _onPlayerResurrected; Log("BoneLib detected."); FusionCompatibility.Detect(); ClimbAnythingCompatibility.Detect(); Log("Initialized."); } public override void OnDeinitializeMelon() { _hud?.Drop(); _detector?.Unsubscribe(); Hooking.OnLevelLoaded -= _onLevelLoaded; Hooking.OnLevelUnloaded -= _onLevelUnloaded; Hooking.OnSwitchAvatarPostfix -= _onAvatarSwitched; Hooking.OnPlayerDeath -= _onPlayerDeath; Hooking.OnPlayerResurrected -= _onPlayerResurrected; } 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(); _hud.Drop(); _haptics.Reset(); _panic.Reset(); } else if (PlayerTracker.IsValid) { _detector.Rescan(); } } if (value) { float deltaTime = Time.deltaTime; _panic.Update(deltaTime); _stamina.Update(deltaTime); _hud.Update(_stamina, deltaTime); _haptics.Update(_stamina, deltaTime); EmitDebugLog(deltaTime); } } private void OnLevelLoaded(LevelInfo info) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) _detector.Clear(); _grips.Clear(); _stamina.ResetFull(); _hud.Drop(); _haptics.Reset(); _panic.Reset(); PlayerTracker.RefreshAvatarMultiplier(); Log("BONELAB player rig acquired (" + info.title + ")."); } private void OnLevelUnloaded() { _detector.Clear(); _grips.Clear(); _hud.Drop(); _haptics.Reset(); _panic.Reset(); } private void OnBlockedClimbAttach(Hand hand, Grip grip) { _grips.QueueDetach(hand); NotifyBlockedRegrab(); } internal static void NotifyBlockedRegrab() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: 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) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown if (!(Time.time - _lastBlockedNotifyStatic < 2.5f)) { _lastBlockedNotifyStatic = Time.time; Notifier.Send(new Notification { Title = NotificationText.op_Implicit("Climb Stamina"), Message = NotificationText.op_Implicit("Too exhausted to climb."), Type = (NotificationType)1, PopupLength = 1.2f, ShowTitleOnPopup = false }); } } 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; } private void OnExhausted() { _haptics.PulseRelease(); } private void OnLowStamina() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_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_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown Notifier.Send(new Notification { Title = NotificationText.op_Implicit("Climb Stamina"), Message = NotificationText.op_Implicit("Grip weakening..."), Type = (NotificationType)0, PopupLength = 1.2f, ShowTitleOnPopup = false }); } private void EmitDebugLog(float deltaTime) { if (!Prefs.DebugLogging.Value) { return; } _debugLogTimer += deltaTime; if (!(_debugLogTimer < 1f)) { _debugLogTimer = 0f; if (PlayerTracker.IsValid) { LogDebug($"left climbing = {_detector.LeftClimbing}, right climbing = {_detector.RightClimbing}, stamina = {_stamina.Stamina:0.0}, state = {_stamina.State}, standing = {PlayerTracker.IsStanding} ({PlayerTracker.DescribeGroundState()}), avatarMult = {PlayerTracker.AvatarDrainMultiplier:0.###}"); } } } 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 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 _pendingDetach = new List(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(); 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) { try { if (!ClimbStaminaMod.ClimbBlocked) { return true; } if ((Object)(object)hand == (Object)null || !PlayerTracker.IsLocalHand(hand)) { return true; } if ((Object)(object)__instance == (Object)null || !ClimbDetector.IsClimbingGrip(__instance)) { return true; } ClimbStaminaMod.NotifyBlockedRegrab(); 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 HapticsController { private const float StartFraction = 0.35f; private const float MinAmplitude = 0.05f; private const float MaxAmplitude = 0.55f; private const float IntensityCurve = 1.6f; private const float MaxInterval = 0.6f; private const float MinInterval = 0.1f; private const float IntervalCurve = 1.3f; private const float MinTickSeconds = 0.03f; private const float MaxTickSeconds = 0.08f; private const float TickFrequencyCalm = 170f; private const float TickFrequencyEmpty = 120f; 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 readonly ClimbDetector _detector; private float _sinceLastPulse; private bool _leftWasClimbing; private bool _rightWasClimbing; internal HapticsController(ClimbDetector detector) { _detector = detector; } internal void Reset() { _sinceLastPulse = 0f; _leftWasClimbing = false; _rightWasClimbing = false; } internal void Update(StaminaController stamina, float deltaTime) { bool leftClimbing = _detector.LeftClimbing; bool rightClimbing = _detector.RightClimbing; bool flag = stamina.State == StaminaState.CLIMBING_ONE_HAND || stamina.State == StaminaState.CLIMBING_TWO_HANDS; float fraction = stamina.Fraction; if (!Prefs.HapticsEnabled.Value || !flag || fraction >= 0.35f) { _sinceLastPulse = 0f; } else { float num = Mathf.Clamp01((0.35f - fraction) / 0.35f); float num2 = Mathf.Lerp(0.6f, 0.1f, Mathf.Pow(num, 1.3f)); _sinceLastPulse += deltaTime; if (_sinceLastPulse >= num2) { _sinceLastPulse = 0f; float amplitude = Mathf.Lerp(0.05f, 0.55f, Mathf.Pow(num, 1.6f)); float seconds = Mathf.Lerp(0.03f, 0.08f, num); float frequency = Mathf.Lerp(170f, 120f, num); if (leftClimbing) { Pulse(Player.LeftController, seconds, frequency, amplitude); } if (rightClimbing) { Pulse(Player.RightController, seconds, frequency, amplitude); } } } _leftWasClimbing = leftClimbing; _rightWasClimbing = rightClimbing; } internal void PulsePanic(Hand hand) { //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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 if (!Prefs.HapticsEnabled.Value || (Object)(object)hand == (Object)null) { 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); } internal void PulseRelease() { if (Prefs.HapticsEnabled.Value) { if (_leftWasClimbing) { Pulse(Player.LeftController, 0.15f, 140f, 0.9f); } if (_rightWasClimbing) { Pulse(Player.RightController, 0.15f, 140f, 0.9f); } } } 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 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 readonly StaminaHud _hud; private float _peakFallSpeed; private float _peakAge; private float _cooldown; internal PanicGrabController(ClimbDetector detector, StaminaController stamina, HapticsController haptics, StaminaHud hud) { _detector = detector; _stamina = stamina; _haptics = haptics; _hud = hud; } 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); _hud.FlashPanic(); _stamina.ApplyPanicGrab(); } } } } internal static class PlayerTracker { private static float _avatarDrainMultiplier = 1f; 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 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 void RefreshAvatarMultiplier() { _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.###}"); } } } internal static class Prefs { internal static MelonPreferences_Category Category; internal static MelonPreferences_Entry Enabled; internal static MelonPreferences_Entry MaxStamina; internal static MelonPreferences_Entry OneHandDrainRate; internal static MelonPreferences_Entry TwoHandDrainRate; internal static MelonPreferences_Entry AscendingMultiplier; internal static MelonPreferences_Entry GroundedRegenRate; internal static MelonPreferences_Entry AirborneRegenRate; internal static MelonPreferences_Entry RecoveryDelay; internal static MelonPreferences_Entry MinRegrabStamina; internal static MelonPreferences_Entry AvatarStrengthScaling; internal static MelonPreferences_Entry AvatarReferenceStrength; internal static MelonPreferences_Entry ShowHud; internal static MelonPreferences_Entry HapticsEnabled; internal static MelonPreferences_Entry HapticStrength; internal static MelonPreferences_Entry PanicGrabEnabled; internal static MelonPreferences_Entry PanicFallSpeed; internal static MelonPreferences_Entry PanicStaminaCost; internal static MelonPreferences_Entry DebugLogging; internal static void Setup() { Category = MelonPreferences.CreateCategory("ClimbStamina", "Climb Stamina"); Enabled = Category.CreateEntry("Enabled", true, "Enabled", "Master switch for the climbing stamina system.", false, false, (ValueValidator)null, (string)null); MaxStamina = Category.CreateEntry("MaxStamina", 100f, "Max Stamina", "Total stamina pool.", false, false, (ValueValidator)null, (string)null); OneHandDrainRate = Category.CreateEntry("OneHandDrainRate", 10f, "One-Hand Drain Rate", "Stamina drained per second while hanging from one hand.", false, false, (ValueValidator)null, (string)null); TwoHandDrainRate = Category.CreateEntry("TwoHandDrainRate", 5f, "Two-Hand Drain Rate", "Stamina drained per second while hanging from two hands.", false, false, (ValueValidator)null, (string)null); AscendingMultiplier = Category.CreateEntry("AscendingMultiplier", 1.3f, "Ascending Multiplier", "Extra drain multiplier while actively climbing upward.", false, false, (ValueValidator)null, (string)null); GroundedRegenRate = Category.CreateEntry("GroundedRegenRate", 20f, "Grounded Regen Rate", "Stamina regenerated per second while standing on the ground.", false, false, (ValueValidator)null, (string)null); AirborneRegenRate = Category.CreateEntry("AirborneRegenRate", 5f, "Airborne Regen Rate", "Stamina regenerated per second while airborne (not climbing).", false, false, (ValueValidator)null, (string)null); RecoveryDelay = Category.CreateEntry("RecoveryDelay", 1f, "Recovery Delay", "Seconds after climbing stops before stamina starts regenerating.", false, false, (ValueValidator)null, (string)null); MinRegrabStamina = Category.CreateEntry("MinRegrabStamina", 100f, "Regrab Stamina %", "After exhaustion, climbing stays blocked until the bar refills to this percent.", false, false, (ValueValidator)null, (string)null); AvatarStrengthScaling = Category.CreateEntry("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("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); ShowHud = Category.CreateEntry("ShowHud", true, "Show Stamina HUD", "World-space stamina bar shown while climbing/recovering.", false, false, (ValueValidator)null, (string)null); HapticsEnabled = Category.CreateEntry("HapticsEnabled", true, "Haptic Feedback", "Pulse the gripping hands' controllers as stamina runs low.", false, false, (ValueValidator)null, (string)null); HapticStrength = Category.CreateEntry("HapticStrength", 1f, "Haptic Strength", "Multiplier on haptic pulse strength (0 = silent, 1 = default).", false, false, (ValueValidator)null, (string)null); PanicGrabEnabled = Category.CreateEntry("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("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("PanicStaminaCost", 20f, "Panic Stamina Cost %", "Stamina lost instantly on a panic grab, as a percent of max.", false, false, (ValueValidator)null, (string)null); DebugLogging = Category.CreateEntry("DebugLogging", false, "Debug Logging", "Verbose state logging for troubleshooting.", false, false, (ValueValidator)null, (string)null); } internal static void Save() { MelonPreferences.Save(); } } 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_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_0321: Unknown result type (might be due to invalid IL or missing references) //IL_035b: Unknown result type (might be due to invalid IL or missing references) //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_03de: Unknown result type (might be due to invalid IL or missing references) //IL_0427: Unknown result type (might be due to invalid IL or missing references) //IL_046f: 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)delegate(bool v) { Prefs.Enabled.Value = v; Prefs.Save(); }); obj.CreateFloat("Max Stamina", Color.white, Prefs.MaxStamina.Value, 10f, 20f, 300f, (Action)delegate(float v) { Prefs.MaxStamina.Value = v; Prefs.Save(); }); obj.CreateFloat("One-Hand Drain Rate", Color.white, Prefs.OneHandDrainRate.Value, 1f, 0f, 60f, (Action)delegate(float v) { Prefs.OneHandDrainRate.Value = v; Prefs.Save(); }); obj.CreateFloat("Two-Hand Drain Rate", Color.white, Prefs.TwoHandDrainRate.Value, 1f, 0f, 60f, (Action)delegate(float v) { Prefs.TwoHandDrainRate.Value = v; Prefs.Save(); }); obj.CreateFloat("Ascending Multiplier", Color.white, Prefs.AscendingMultiplier.Value, 0.05f, 1f, 3f, (Action)delegate(float v) { Prefs.AscendingMultiplier.Value = v; Prefs.Save(); }); obj.CreateFloat("Grounded Regen Rate", Color.white, Prefs.GroundedRegenRate.Value, 1f, 0f, 100f, (Action)delegate(float v) { Prefs.GroundedRegenRate.Value = v; Prefs.Save(); }); obj.CreateFloat("Airborne Regen Rate", Color.white, Prefs.AirborneRegenRate.Value, 1f, 0f, 100f, (Action)delegate(float v) { Prefs.AirborneRegenRate.Value = v; Prefs.Save(); }); obj.CreateFloat("Recovery Delay", Color.white, Prefs.RecoveryDelay.Value, 0.25f, 0f, 10f, (Action)delegate(float v) { Prefs.RecoveryDelay.Value = v; Prefs.Save(); }); obj.CreateFloat("Regrab Stamina %", Color.white, Prefs.MinRegrabStamina.Value, 5f, 0f, 100f, (Action)delegate(float v) { Prefs.MinRegrabStamina.Value = v; Prefs.Save(); }); obj.CreateBool("Avatar Strength Scaling", Color.white, Prefs.AvatarStrengthScaling.Value, (Action)delegate(bool v) { Prefs.AvatarStrengthScaling.Value = v; Prefs.Save(); PlayerTracker.RefreshAvatarMultiplier(); }); obj.CreateBool("Show Stamina HUD", Color.white, Prefs.ShowHud.Value, (Action)delegate(bool v) { Prefs.ShowHud.Value = v; Prefs.Save(); }); obj.CreateBool("Haptic Feedback", Color.white, Prefs.HapticsEnabled.Value, (Action)delegate(bool v) { Prefs.HapticsEnabled.Value = v; Prefs.Save(); }); obj.CreateFloat("Haptic Strength", Color.white, Prefs.HapticStrength.Value, 0.1f, 0f, 2f, (Action)delegate(float v) { Prefs.HapticStrength.Value = v; Prefs.Save(); }); obj.CreateBool("Panic Grab", Color.white, Prefs.PanicGrabEnabled.Value, (Action)delegate(bool v) { Prefs.PanicGrabEnabled.Value = v; Prefs.Save(); }); obj.CreateFloat("Panic Fall Speed", Color.white, Prefs.PanicFallSpeed.Value, 0.5f, 1f, 12f, (Action)delegate(float v) { Prefs.PanicFallSpeed.Value = v; Prefs.Save(); }); obj.CreateFloat("Panic Stamina Cost %", Color.white, Prefs.PanicStaminaCost.Value, 5f, 0f, 60f, (Action)delegate(float v) { Prefs.PanicStaminaCost.Value = v; Prefs.Save(); }); obj.CreateBool("Debug Logging", Color.white, Prefs.DebugLogging.Value, (Action)delegate(bool v) { Prefs.DebugLogging.Value = v; Prefs.Save(); }); } } internal enum StaminaState { READY, CLIMBING_ONE_HAND, CLIMBING_TWO_HANDS, RECOVERY_DELAY, RECOVERING, EXHAUSTED } internal sealed class StaminaController { private const float AscendDeadZone = 0.3f; private const float AscendSmoothingRate = 8f; internal const float LowStaminaFraction = 0.25f; private const float LowStaminaRearmFraction = 0.3f; private const float PanicDrainWindowSeconds = 1f; private const float PanicDrainMultiplier = 1.75f; private readonly ClimbDetector _detector; private readonly GripController _grips; private float _stamina; private float _timeSinceDrain = float.MaxValue; private float _smoothedVerticalVelocity; private bool _wasSupporting; private float _panicDrainRemaining; internal Action OnExhausted; internal Action OnLowStamina; private bool _lowStaminaSignaled; 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 IsExhausted => State == StaminaState.EXHAUSTED; internal StaminaController(ClimbDetector detector, GripController grips) { _detector = detector; _grips = grips; _stamina = MaxStamina; _detector.CanStartClimb = () => !IsExhausted; } internal void ResetFull() { _stamina = MaxStamina; _timeSinceDrain = float.MaxValue; _smoothedVerticalVelocity = 0f; _wasSupporting = false; _panicDrainRemaining = 0f; _lowStaminaSignaled = false; State = StaminaState.READY; ClimbStaminaMod.ClimbBlocked = false; } internal void Update(float deltaTime) { if (!PlayerTracker.IsValid) { _detector.Clear(); _grips.Clear(); _smoothedVerticalVelocity = 0f; return; } _grips.ProcessQueue(); _detector.Validate(); 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(); _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; _lowStaminaSignaled = 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); float num2 = ((climbingHandCount >= 2) ? Prefs.TwoHandDrainRate.Value : Prefs.OneHandDrainRate.Value); if (_smoothedVerticalVelocity > 0.3f) { num2 *= Mathf.Max(1f, Prefs.AscendingMultiplier.Value); } num2 *= PlayerTracker.AvatarDrainMultiplier; if (_panicDrainRemaining > 0f) { num2 *= 1.75f; } _stamina -= Mathf.Max(0f, num2) * deltaTime; _timeSinceDrain = 0f; if (!_lowStaminaSignaled && Fraction < 0.25f) { _lowStaminaSignaled = true; OnLowStamina?.Invoke(); } if (_stamina <= 0f) { _stamina = 0f; Exhaust(deferRelease: false); } } else if (_stamina >= maxStamina) { State = StaminaState.READY; _lowStaminaSignaled = false; } else if (_timeSinceDrain < Prefs.RecoveryDelay.Value) { State = StaminaState.RECOVERY_DELAY; } else { State = StaminaState.RECOVERING; Regenerate(deltaTime, isStanding); if (_lowStaminaSignaled && Fraction >= 0.3f) { _lowStaminaSignaled = false; } if (_stamina >= maxStamina) { _stamina = maxStamina; State = StaminaState.READY; _lowStaminaSignaled = false; } } } internal void ApplyPanicGrab() { float num = MaxStamina * Mathf.Clamp01(Prefs.PanicStaminaCost.Value / 100f); _stamina -= num; _timeSinceDrain = 0f; _panicDrainRemaining = 1f; if (_stamina <= 0f) { _stamina = 0f; Exhaust(deferRelease: true); } } private void Regenerate(float deltaTime, bool standing) { if (!(_timeSinceDrain < Prefs.RecoveryDelay.Value)) { float num = (standing ? Prefs.GroundedRegenRate.Value : Prefs.AirborneRegenRate.Value); _stamina = Mathf.Min(MaxStamina, _stamina + Mathf.Max(0f, num) * deltaTime); } } private void UpdateAscendSmoothing(float deltaTime, bool supporting) { if (!supporting) { _smoothedVerticalVelocity = 0f; return; } float verticalVelocity = PlayerTracker.VerticalVelocity; float num = 1f - Mathf.Exp(-8f * deltaTime); _smoothedVerticalVelocity = Mathf.Lerp(_smoothedVerticalVelocity, verticalVelocity, num); } private void Exhaust(bool deferRelease) { State = StaminaState.EXHAUSTED; ClimbStaminaMod.ClimbBlocked = true; _timeSinceDrain = 0f; if (deferRelease) { _detector.ForEachClimbingHand(_grips.QueueDetach); } else { _detector.ForEachClimbingHand(GripController.ReleaseClimbingHand); } _detector.Clear(); ClimbStaminaMod.LogDebug("Stamina exhausted; climbing grips released"); OnExhausted?.Invoke(); } } internal sealed class StaminaHud { private const float FollowDistance = 0.72f; private const float FollowDrop = 0.16f; private const float FollowRate = 5f; private const float FadeRate = 7f; private const float FullLingerSeconds = 1.2f; private const float WallClearance = 0.06f; private const float MinFollowDistance = 0.1f; private const float AbsoluteMinDistance = 0.055f; private const float BaseScale = 0.00075f; private const float SnapDistance = 2.5f; private const float WarnFraction = 0.5f; private static readonly float NominalDistance = Mathf.Sqrt(0.544f); private static readonly Color BarNormal = new Color(0.55f, 0.85f, 0.55f, 1f); private static readonly Color BarWarn = new Color(0.93f, 0.78f, 0.35f, 1f); private static readonly Color BarLow = new Color(0.9f, 0.35f, 0.28f, 1f); private static readonly Color BarExhausted = new Color(0.82f, 0.16f, 0.12f, 1f); private static readonly Color LabelExhausted = new Color(1f, 0.55f, 0.5f, 1f); private readonly string[] _percentText; private const float MinFillFraction = 0.11f; private GameObject _root; private CanvasGroup _group; private RectTransform _fillRect; private Image _fillImage; private TextMeshProUGUI _label; private Texture2D _bgTexture; private Sprite _bgSprite; private Texture2D _fillTexture; private Sprite _fillSprite; private Texture2D _outlineTexture; private Sprite _outlineSprite; private Texture2D _glowTexture; private Sprite _glowSprite; private Image _glowImage; private float _glowAlpha; private float _alpha; private float _fullSince = -999f; private int _shownPercent = -1; private bool _shownExhausted; private float _shownFraction = -1f; private int _shownColorBand = -1; internal StaminaHud() { _percentText = new string[101]; for (int i = 0; i <= 100; i++) { _percentText[i] = i + "%"; } } internal void Drop() { if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } DestroyVisualAssets(); _root = null; _group = null; _fillRect = null; _fillImage = null; _label = null; _glowImage = null; _glowAlpha = 0f; _alpha = 0f; _shownPercent = -1; _shownFraction = -1f; _shownColorBand = -1; } private void DestroyVisualAssets() { if ((Object)(object)_bgSprite != (Object)null) { Object.Destroy((Object)(object)_bgSprite); } if ((Object)(object)_bgTexture != (Object)null) { Object.Destroy((Object)(object)_bgTexture); } if ((Object)(object)_fillSprite != (Object)null) { Object.Destroy((Object)(object)_fillSprite); } if ((Object)(object)_fillTexture != (Object)null) { Object.Destroy((Object)(object)_fillTexture); } if ((Object)(object)_outlineSprite != (Object)null) { Object.Destroy((Object)(object)_outlineSprite); } if ((Object)(object)_outlineTexture != (Object)null) { Object.Destroy((Object)(object)_outlineTexture); } if ((Object)(object)_glowSprite != (Object)null) { Object.Destroy((Object)(object)_glowSprite); } if ((Object)(object)_glowTexture != (Object)null) { Object.Destroy((Object)(object)_glowTexture); } _bgSprite = null; _bgTexture = null; _fillSprite = null; _fillTexture = null; _outlineSprite = null; _outlineTexture = null; _glowSprite = null; _glowTexture = null; } internal void Update(StaminaController stamina, float deltaTime) { bool flag = ComputeVisibility(stamina); if ((Object)(object)_root == (Object)null) { if (!flag || !PlayerTracker.IsValid || !Build()) { return; } SnapToTarget(); } float num = (flag ? 1f : 0f); _alpha = Mathf.Lerp(_alpha, num, 1f - Mathf.Exp(-7f * deltaTime)); if (_alpha < 0.01f && !flag) { if (_root.activeSelf) { _root.SetActive(false); } return; } if (!_root.activeSelf) { _root.SetActive(true); SnapToTarget(); } _group.alpha = _alpha; Follow(deltaTime); Refresh(stamina, deltaTime); } private bool ComputeVisibility(StaminaController stamina) { if (!Prefs.ShowHud.Value || !PlayerTracker.IsValid) { return false; } if (stamina.Fraction < 1f || stamina.IsExhausted) { _fullSince = Time.time; return true; } return Time.time - _fullSince < 1.2f; } private void Refresh(StaminaController stamina, float deltaTime) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0091: 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_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_01f5: 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_017e: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) float fraction = stamina.Fraction; if (fraction <= 0.001f) { if (((Behaviour)_fillImage).enabled) { ((Behaviour)_fillImage).enabled = false; } } else { if (!((Behaviour)_fillImage).enabled) { ((Behaviour)_fillImage).enabled = true; } if (fraction != _shownFraction) { _shownFraction = fraction; _fillRect.anchorMax = new Vector2(Mathf.Max(fraction, 0.11f), 1f); } } bool isExhausted = stamina.IsExhausted; int num; Color color; if (isExhausted) { num = 3; color = BarExhausted; } else if (fraction < 0.25f) { num = 2; color = BarLow; } else if (fraction < 0.5f) { num = 1; color = BarWarn; } else { num = 0; color = BarNormal; } if (num != _shownColorBand) { _shownColorBand = num; ((Graphic)_fillImage).color = color; } if ((Object)(object)_glowImage != (Object)null) { float num2 = ((isExhausted || fraction < 0.25f) ? (0.45f + 0.25f * Mathf.Sin(Time.time * 5f)) : 0f); _glowAlpha = Mathf.Lerp(_glowAlpha, num2, 1f - Mathf.Exp(-6f * deltaTime)); if (_glowAlpha < 0.01f) { if (((Behaviour)_glowImage).enabled) { ((Behaviour)_glowImage).enabled = false; } } else { if (!((Behaviour)_glowImage).enabled) { ((Behaviour)_glowImage).enabled = true; } Color val = (isExhausted ? BarExhausted : BarLow); ((Graphic)_glowImage).color = new Color(val.r, val.g, val.b, _glowAlpha); } } if ((Object)(object)_label == (Object)null) { return; } if (isExhausted) { if (!_shownExhausted) { _shownExhausted = true; _shownPercent = -1; ((TMP_Text)_label).text = "EXHAUSTED"; ((Graphic)_label).color = LabelExhausted; } return; } _shownExhausted = false; int num3 = Mathf.Clamp(Mathf.RoundToInt(fraction * 100f), 0, 100); if (num3 != _shownPercent) { _shownPercent = num3; ((TMP_Text)_label).text = _percentText[num3]; ((Graphic)_label).color = Color.white; } } internal void FlashPanic() { _glowAlpha = Mathf.Max(_glowAlpha, 0.85f); } private void Follow(float deltaTime) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_0040: 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: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: 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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) Transform head = Player.Head; if ((Object)(object)head == (Object)null) { return; } Vector3 val = TargetPosition(head); Transform transform = _root.transform; Vector3 val2 = transform.position - val; if (((Vector3)(ref val2)).sqrMagnitude > 6.25f) { transform.position = val; Vector3 val3 = val - head.position; if (((Vector3)(ref val3)).sqrMagnitude > 1E-06f) { transform.rotation = Quaternion.LookRotation(val3, Vector3.up); } ApplyDistanceScale(((Vector3)(ref val3)).magnitude); return; } float num = 1f - Mathf.Exp(-5f * deltaTime); Vector3 val4 = Vector3.Lerp(transform.position, val, num); Vector3 val5 = val4 - head.position; float num2 = ((Vector3)(ref val5)).magnitude; if (num2 > 0.001f) { Vector3 val6 = val5 / num2; float num3 = ClampToGeometry(head.position, val6, num2); if (num3 < num2) { num2 = num3; val4 = head.position + val6 * num2; } } transform.position = val4; Vector3 val7 = val4 - head.position; if (((Vector3)(ref val7)).sqrMagnitude > 1E-06f) { Quaternion val8 = Quaternion.LookRotation(val7, Vector3.up); transform.rotation = Quaternion.Slerp(transform.rotation, val8, num); } ApplyDistanceScale(num2); } private void SnapToTarget() { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0041: 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_004d: 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_005d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) Transform head = Player.Head; if (!((Object)(object)head == (Object)null) && !((Object)(object)_root == (Object)null)) { Vector3 val = TargetPosition(head); _root.transform.position = val; _root.transform.rotation = Quaternion.LookRotation(val - head.position, Vector3.up); Vector3 val2 = val - head.position; ApplyDistanceScale(((Vector3)(ref val2)).magnitude); } } private static Vector3 TargetPosition(Transform head) { //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_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: 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_0052: 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_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: 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) Vector3 forward = head.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude < 0.001f) { forward = Vector3.forward; } ((Vector3)(ref forward)).Normalize(); Vector3 val = (forward * 0.72f + Vector3.down * 0.16f) / NominalDistance; float num = ClampToGeometry(head.position, val, NominalDistance); return head.position + val * num; } private static float ClampToGeometry(Vector3 origin, Vector3 dir, float desired) { //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) RaycastHit val = default(RaycastHit); if (!Physics.Raycast(origin, dir, ref val, desired + 0.06f, -5, (QueryTriggerInteraction)1)) { return desired; } Collider collider = ((RaycastHit)(ref val)).collider; if ((Object)(object)collider == (Object)null || (Object)(object)collider.attachedRigidbody != (Object)null) { return desired; } float num = ((RaycastHit)(ref val)).distance - 0.06f; if (num >= 0.1f) { return Mathf.Min(num, desired); } return Mathf.Max(((RaycastHit)(ref val)).distance * 0.75f, 0.055f); } private void ApplyDistanceScale(float distance) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) float num = 0.00075f * Mathf.Clamp(distance / NominalDistance, 0.05f, 1f); _root.transform.localScale = new Vector3(num, num, num); } private bool Build() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0119: 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) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Expected O, but got Unknown //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_02eb: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Expected O, but got Unknown //IL_031a: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_02ac: Unknown result type (might be due to invalid IL or missing references) //IL_02bc: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_0361: Unknown result type (might be due to invalid IL or missing references) //IL_0371: 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_03a5: Unknown result type (might be due to invalid IL or missing references) //IL_03bf: Unknown result type (might be due to invalid IL or missing references) //IL_03cf: Unknown result type (might be due to invalid IL or missing references) //IL_03f0: Unknown result type (might be due to invalid IL or missing references) //IL_03f7: Expected O, but got Unknown //IL_046c: Unknown result type (might be due to invalid IL or missing references) //IL_0477: Unknown result type (might be due to invalid IL or missing references) //IL_0482: Unknown result type (might be due to invalid IL or missing references) //IL_048c: Unknown result type (might be due to invalid IL or missing references) DestroyVisualAssets(); _root = new GameObject("ClimbStamina_HUD"); Canvas obj = _root.AddComponent(); obj.renderMode = (RenderMode)2; obj.sortingOrder = 100; _group = _root.AddComponent(); _group.interactable = false; _group.blocksRaycasts = false; _group.alpha = 0f; RectTransform component = _root.GetComponent(); component.sizeDelta = new Vector2(276f, 36f); ((Transform)component).localScale = new Vector3(0.00075f, 0.00075f, 0.00075f); _bgSprite = CreateRoundedSprite(10, out _bgTexture); _fillSprite = CreateRoundedSprite(8, out _fillTexture); _outlineSprite = CreateRoundedOutlineSprite(10, 1.5f, soft: false, out _outlineTexture); _glowSprite = CreateRoundedOutlineSprite(10, 8f, soft: true, out _glowTexture); GameObject val = new GameObject("Background"); val.transform.SetParent(_root.transform, false); Image val2 = val.AddComponent(); ((Graphic)val2).color = new Color(0f, 0f, 0f, 0.55f); if ((Object)(object)_bgSprite != (Object)null) { val2.sprite = _bgSprite; val2.type = (Type)1; } RectTransform component2 = val.GetComponent(); component2.anchorMin = Vector2.zero; component2.anchorMax = Vector2.one; component2.offsetMin = Vector2.zero; component2.offsetMax = Vector2.zero; if ((Object)(object)_glowSprite != (Object)null) { GameObject val3 = new GameObject("Glow"); val3.transform.SetParent(_root.transform, false); _glowImage = val3.AddComponent(); _glowImage.sprite = _glowSprite; _glowImage.type = (Type)1; ((Graphic)_glowImage).color = new Color(0f, 0f, 0f, 0f); ((Behaviour)_glowImage).enabled = false; RectTransform component3 = val3.GetComponent(); component3.anchorMin = Vector2.zero; component3.anchorMax = Vector2.one; component3.offsetMin = Vector2.zero; component3.offsetMax = Vector2.zero; } if ((Object)(object)_outlineSprite != (Object)null) { GameObject val4 = new GameObject("Outline"); val4.transform.SetParent(_root.transform, false); Image obj2 = val4.AddComponent(); obj2.sprite = _outlineSprite; obj2.type = (Type)1; ((Graphic)obj2).color = new Color(0.38f, 0.38f, 0.38f, 0.85f); RectTransform component4 = val4.GetComponent(); component4.anchorMin = Vector2.zero; component4.anchorMax = Vector2.one; component4.offsetMin = Vector2.zero; component4.offsetMax = Vector2.zero; } GameObject val5 = new GameObject("Fill"); val5.transform.SetParent(_root.transform, false); _fillImage = val5.AddComponent(); ((Graphic)_fillImage).color = BarNormal; if ((Object)(object)_fillSprite != (Object)null) { _fillImage.sprite = _fillSprite; _fillImage.type = (Type)1; } _fillRect = val5.GetComponent(); _fillRect.anchorMin = Vector2.zero; _fillRect.anchorMax = Vector2.one; _fillRect.pivot = new Vector2(0f, 0.5f); _fillRect.offsetMin = new Vector2(4f, 4f); _fillRect.offsetMax = new Vector2(-4f, -4f); ((Transform)_fillRect).localScale = Vector3.one; TMP_FontAsset val6 = ResolveFont(); if ((Object)(object)val6 != (Object)null) { GameObject val7 = new GameObject("Label"); val7.transform.SetParent(_root.transform, false); _label = val7.AddComponent(); ((TMP_Text)_label).font = val6; ((TMP_Text)_label).fontSize = 20f; ((TMP_Text)_label).alignment = (TextAlignmentOptions)514; ((TMP_Text)_label).enableWordWrapping = false; ((TMP_Text)_label).text = string.Empty; RectTransform component5 = val7.GetComponent(); component5.anchorMin = Vector2.zero; component5.anchorMax = Vector2.one; component5.offsetMin = Vector2.zero; component5.offsetMax = Vector2.zero; } else { ClimbStaminaMod.LogDebug("No TMP font asset found; HUD runs bar-only"); } _shownPercent = -1; _shownExhausted = false; _shownFraction = -1f; _shownColorBand = -1; return true; } private static Sprite CreateRoundedSprite(int radius, out Texture2D texture) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) texture = null; try { int num = radius * 2 + 6; texture = new Texture2D(num, num, (TextureFormat)4, false); ((Texture)texture).wrapMode = (TextureWrapMode)1; ((Texture)texture).filterMode = (FilterMode)1; Color32[] array = (Color32[])(object)new Color32[num * num]; float num2 = (float)num * 0.5f; float num3 = num2 - (float)radius; for (int i = 0; i < num; i++) { for (int j = 0; j < num; j++) { float num4 = Mathf.Max(Mathf.Abs((float)j + 0.5f - num2) - num3, 0f); float num5 = Mathf.Max(Mathf.Abs((float)i + 0.5f - num2) - num3, 0f); float num6 = Mathf.Sqrt(num4 * num4 + num5 * num5) - ((float)radius - 0.5f); float num7 = Mathf.Clamp01(0.5f - num6); array[i * num + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(num7 * 255f)); } } texture.SetPixels32(Il2CppStructArray.op_Implicit(array)); texture.Apply(false, true); float num8 = (float)radius + 2f; return Sprite.Create(texture, new Rect(0f, 0f, (float)num, (float)num), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4(num8, num8, num8, num8)); } catch (Exception ex) { ClimbStaminaMod.LogDebug("Rounded sprite creation failed (" + ex.GetType().Name + "); HUD keeps square corners"); if ((Object)(object)texture != (Object)null) { Object.Destroy((Object)(object)texture); } texture = null; return null; } } private static Sprite CreateRoundedOutlineSprite(int radius, float thickness, bool soft, out Texture2D texture) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Expected O, but got Unknown //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_018e: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) texture = null; try { int num = Mathf.CeilToInt(thickness * 0.5f + 1.5f); int num2 = radius * 2 + num * 2 + 4; texture = new Texture2D(num2, num2, (TextureFormat)4, false); ((Texture)texture).wrapMode = (TextureWrapMode)1; ((Texture)texture).filterMode = (FilterMode)1; Color32[] array = (Color32[])(object)new Color32[num2 * num2]; float num3 = (float)num2 * 0.5f; float num4 = num3 - (float)num - (float)radius; for (int i = 0; i < num2; i++) { for (int j = 0; j < num2; j++) { float num5 = Mathf.Max(Mathf.Abs((float)j + 0.5f - num3) - num4, 0f); float num6 = Mathf.Max(Mathf.Abs((float)i + 0.5f - num3) - num4, 0f); float num7 = Mathf.Abs(Mathf.Sqrt(num5 * num5 + num6 * num6) - (float)radius); float num8; if (soft) { num8 = Mathf.Clamp01(1f - num7 / (thickness * 0.5f)); num8 *= num8; } else { num8 = Mathf.Clamp01(thickness * 0.5f + 0.5f - num7); } array[i * num2 + j] = new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)(num8 * 255f)); } } texture.SetPixels32(Il2CppStructArray.op_Implicit(array)); texture.Apply(false, true); float num9 = (float)(radius + num) + 1f; return Sprite.Create(texture, new Rect(0f, 0f, (float)num2, (float)num2), new Vector2(0.5f, 0.5f), 100f, 0u, (SpriteMeshType)0, new Vector4(num9, num9, num9, num9)); } catch (Exception ex) { ClimbStaminaMod.LogDebug("Outline sprite creation failed (" + ex.GetType().Name + "); HUD runs without an outline"); if ((Object)(object)texture != (Object)null) { Object.Destroy((Object)(object)texture); } texture = null; return null; } } private static TMP_FontAsset ResolveFont() { TMP_FontAsset defaultFontAsset = TMP_Settings.defaultFontAsset; if ((Object)(object)defaultFontAsset != (Object)null) { return defaultFontAsset; } Il2CppReferenceArray val = Resources.FindObjectsOfTypeAll(Il2CppType.Of()); if (val != null && ((Il2CppArrayBase)(object)val).Length > 0) { return ((Il2CppObjectBase)((Il2CppArrayBase)(object)val)[0]).TryCast(); } return null; } } } namespace ClimbStamina.Compatibility { internal static class ClimbAnythingCompatibility { internal static bool ClimbAnythingDetected { get; private set; } internal static void Detect() { foreach (MelonBase registeredMelon in MelonBase.RegisteredMelons) { MelonInfoAttribute info = registeredMelon.Info; string text = ((info != null) ? info.Name : null); if (text != null && text.Replace(" ", "").ToLowerInvariant().Contains("climbanything")) { ClimbAnythingDetected = true; ClimbStaminaMod.Log("'" + text + "' detected; its climbing grips are covered by stamina rules automatically."); break; } } } } internal static class FusionCompatibility { internal static bool FusionDetected { get; private set; } internal static void Detect() { FusionDetected = MelonBase.FindMelon("LabFusion", "Lakatrazz") != null; if (FusionDetected) { ClimbStaminaMod.Log("Fusion detected; local-only mode enabled."); } } } }