Decompiled source of ClimbAnythingWeaponFix v1.0.2

Mods/ClimbAnythingWeaponFix.dll

Decompiled 8 hours ago
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BoneLib;
using BoneLib.BoneMenu;
using ClimbAnythingWeaponFix;
using HarmonyLib;
using Il2CppSLZ.Marrow;
using Il2CppSLZ.Marrow.Interaction;
using MelonLoader;
using MelonLoader.Preferences;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(WeaponFixMod), "ClimbAnythingWeaponFix", "1.0.2", "Cesar", null)]
[assembly: MelonGame("Stress Level Zero", "BONELAB")]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("ClimbAnythingWeaponFix")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2+3d05db3eeca51d4ece554de84629e696d426294e")]
[assembly: AssemblyProduct("ClimbAnythingWeaponFix")]
[assembly: AssemblyTitle("ClimbAnythingWeaponFix")]
[assembly: AssemblyVersion("1.0.2.0")]
namespace ClimbAnythingWeaponFix;

internal static class ClimbAnythingBridge
{
	private static bool _looked;

	private static Harmony _harmony;

	private static object _melon;

	private static FieldInfo _leftStateField;

	private static FieldInfo _rightStateField;

	private static FieldInfo _handField;

	private static FieldInfo _climbPointField;

	private static FieldInfo _isClimbingField;

	private static MethodInfo _stopClimbing;

	private static int _faults;

	private static float _lastFaultAt = float.NegativeInfinity;

	private const int MaxFaults = 5;

	private const float FaultWindow = 10f;

	internal static bool Active { get; private set; }

	internal static void Initialize(Harmony harmony)
	{
		_harmony = harmony;
		if (!_looked)
		{
			MelonBase val = MelonBase.FindMelon("ClimbAnything", "Waspothegreat");
			if (val != null)
			{
				_looked = true;
				TryBind(val);
			}
		}
	}

	internal static void Retry(Harmony harmony)
	{
		if (_harmony == null)
		{
			_harmony = harmony;
		}
		if (!_looked && _harmony != null)
		{
			_looked = true;
			MelonBase val = MelonBase.FindMelon("ClimbAnything", "Waspothegreat");
			if (val == null)
			{
				Log.Msg("ClimbAnything is not installed — nothing to fix, staying out of the way.");
			}
			else
			{
				TryBind(val);
			}
		}
	}

	private static void TryBind(MelonBase melon)
	{
		//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_00e1: Expected O, but got Unknown
		try
		{
			Type type = ((object)melon).GetType();
			_leftStateField = type.GetField("_leftHandState", BindingFlags.Instance | BindingFlags.NonPublic);
			_rightStateField = type.GetField("_rightHandState", BindingFlags.Instance | BindingFlags.NonPublic);
			Type nestedType = type.GetNestedType("HandState", BindingFlags.Public | BindingFlags.NonPublic);
			_handField = nestedType?.GetField("Hand", BindingFlags.Instance | BindingFlags.Public);
			_climbPointField = nestedType?.GetField("ClimbPoint", BindingFlags.Instance | BindingFlags.Public);
			_isClimbingField = nestedType?.GetField("IsClimbing", BindingFlags.Instance | BindingFlags.Public);
			MethodInfo method = type.GetMethod("StartClimbing", BindingFlags.Instance | BindingFlags.NonPublic);
			_stopClimbing = type.GetMethod("StopClimbing", BindingFlags.Instance | BindingFlags.NonPublic);
			if (!ShapeIsSupported(method, nestedType))
			{
				Log.Warning("ClimbAnything is installed but its internals don't match the supported layout (verified against 1.0.0 / package 1.0.1). This fix is disabled; ClimbAnything itself is untouched.");
				return;
			}
			_melon = melon;
			_harmony.Patch((MethodBase)method, new HarmonyMethod(typeof(ClimbAnythingBridge), "StartClimbingPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			Active = true;
			MelonInfoAttribute info = melon.Info;
			Log.Msg("Patched ClimbAnything " + (((info != null) ? info.Version : null) ?? "?") + " — per hand: climbs on an occupied hand are blocked, and a climb needs a fresh grip press rather than a grip that was already held.");
		}
		catch (Exception ex)
		{
			Active = false;
			Log.Warning($"Could not patch ClimbAnything ({ex.GetType().Name}: {ex.Message}). " + "The fix is disabled; ClimbAnything itself is untouched.");
		}
	}

	private static bool ShapeIsSupported(MethodInfo startClimbing, Type handStateType)
	{
		if (handStateType == null || startClimbing == null || _stopClimbing == null)
		{
			return false;
		}
		if (_leftStateField == null || _rightStateField == null)
		{
			return false;
		}
		if (_handField == null || _climbPointField == null || _isClimbingField == null)
		{
			return false;
		}
		if (_handField.FieldType != typeof(Hand))
		{
			return false;
		}
		if (_isClimbingField.FieldType != typeof(bool))
		{
			return false;
		}
		if (!typeof(Object).IsAssignableFrom(_climbPointField.FieldType))
		{
			return false;
		}
		ParameterInfo[] parameters = startClimbing.GetParameters();
		if (parameters.Length != 2 || parameters[0].ParameterType != handStateType)
		{
			return false;
		}
		ParameterInfo[] parameters2 = _stopClimbing.GetParameters();
		if (parameters2.Length != 1 || parameters2[0].ParameterType != handStateType)
		{
			return false;
		}
		return true;
	}

	private static bool StartClimbingPrefix(object __0, object __1)
	{
		if (!Active || (!Prefs.Enabled && !Prefs.RequireGripPress))
		{
			return true;
		}
		try
		{
			object? value = _handField.GetValue(__0);
			Hand val = (Hand)((value is Hand) ? value : null);
			if ((Object)(object)val == (Object)null)
			{
				return true;
			}
			Collider target = (Collider)((__1 is Collider) ? __1 : null);
			if (Prefs.Enabled)
			{
				HandOccupancy.Reason reason = HandOccupancy.Evaluate(val);
				if (reason != HandOccupancy.Reason.Free)
				{
					Diagnostics.NoteBlocked(val, reason, target);
					return false;
				}
			}
			if (Prefs.RequireGripPress && GripIntent.Authorize(val) == GripPressTracker.Verdict.Stale)
			{
				Diagnostics.NoteStaleGrip(val, target);
				return false;
			}
			Diagnostics.NoteAllowed(val, target);
			return true;
		}
		catch (Exception e)
		{
			Fault(e);
			return true;
		}
	}

	internal static void Watchdog()
	{
		if (!Active || !Prefs.Enabled || _melon == null)
		{
			return;
		}
		try
		{
			CheckState(_leftStateField.GetValue(_melon));
			CheckState(_rightStateField.GetValue(_melon));
		}
		catch (Exception e)
		{
			Fault(e);
		}
	}

	private static void CheckState(object state)
	{
		if (state != null && (bool)_isClimbingField.GetValue(state) && !((Object)/*isinst with value type is only supported in some contexts*/ == (Object)null))
		{
			object? value = _handField.GetValue(state);
			Hand hand = (Hand)((value is Hand) ? value : null);
			HandOccupancy.Reason reason = HandOccupancy.Attached(hand);
			if (reason != HandOccupancy.Reason.Free)
			{
				_stopClimbing.Invoke(_melon, new object[1] { state });
				Diagnostics.NoteWatchdog(hand, reason);
			}
		}
	}

	private static void Fault(Exception e)
	{
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (realtimeSinceStartup - _lastFaultAt > 10f)
		{
			_faults = 0;
		}
		_lastFaultAt = realtimeSinceStartup;
		_faults++;
		if (_faults == 1)
		{
			Log.Warning("Error while guarding ClimbAnything: " + e.GetType().Name + ": " + e.Message);
		}
		if (_faults >= 5 && Active)
		{
			Active = false;
			Log.Warning("Disabling the ClimbAnything fixes after repeated errors. ClimbAnything continues to run exactly as it does unpatched.");
		}
	}
}
internal static class Diagnostics
{
	private sealed class Episode
	{
		public bool Open;

		public int StartFrame;

		public float StartTime;

		public float PeakGrip;

		public int GameGrabbedFrame = -1;

		public int AttachedFrame = -1;

		public bool SawHover;

		public string HoverName;

		public bool Blocked;

		public bool GatePassed;

		public string AllowedTarget;

		public int Lines;
	}

	private const float ClimbAnythingGripThreshold = 0.01f;

	private static readonly Episode LeftEpisode = new Episode();

	private static readonly Episode RightEpisode = new Episode();

	private static bool _thresholdsLogged;

	private const int MaxLinesPerEpisode = 6;

	private static float _lastBlockedLogL = -99f;

	private static float _lastBlockedLogR = -99f;

	private static float _lastStaleGripLogL = -99f;

	private static float _lastStaleGripLogR = -99f;

	private static float _lastWatchdogLogL = -99f;

	private static float _lastWatchdogLogR = -99f;

	private const float BlockedLogInterval = 1f;

	private static bool Due(Hand hand, ref float left, ref float right)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Invalid comparison between Unknown and I4
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		bool flag = (int)SideOf(hand) != 2;
		float num = (flag ? left : right);
		if (realtimeSinceStartup - num < 1f)
		{
			return false;
		}
		if (flag)
		{
			left = realtimeSinceStartup;
		}
		else
		{
			right = realtimeSinceStartup;
		}
		return true;
	}

	internal static void Tick()
	{
		if (!Prefs.Diagnostics)
		{
			return;
		}
		try
		{
			if (!Player.ControllersExist || (Object)(object)Player.RigManager == (Object)null)
			{
				Abandon(LeftEpisode);
				Abandon(RightEpisode);
			}
			else
			{
				LogThresholdsOnce();
				Track(Player.LeftHand, Player.LeftController, LeftEpisode, "L");
				Track(Player.RightHand, Player.RightController, RightEpisode, "R");
			}
		}
		catch
		{
		}
	}

	private static void LogThresholdsOnce()
	{
		if (!_thresholdsLogged)
		{
			_thresholdsLogged = true;
			Log.Msg($"[diag] ClimbAnything treats grip > {0.01f:F3} as held. " + "Marrow does not attach until its own, much higher, grab threshold is crossed — the gap between the two is the window this mod closes. Watch the 'grab threshold crossed' line below for where it actually lands on this rig.");
		}
	}

	private static void Track(Hand hand, BaseController controller, Episode ep, string side)
	{
		if ((Object)(object)hand == (Object)null || (Object)(object)controller == (Object)null)
		{
			Abandon(ep);
			return;
		}
		float gripForce;
		bool flag;
		try
		{
			gripForce = controller.GetGripForce();
			flag = controller.IsGrabbed();
		}
		catch
		{
			Abandon(ep);
			return;
		}
		bool flag2 = gripForce > 0.01f;
		if (!ep.Open)
		{
			if (flag2)
			{
				ep.Open = true;
				ep.StartFrame = Time.frameCount;
				ep.StartTime = Time.realtimeSinceStartup;
				ep.PeakGrip = gripForce;
				ep.GameGrabbedFrame = -1;
				ep.AttachedFrame = -1;
				ep.SawHover = false;
				ep.HoverName = null;
				ep.Blocked = false;
				ep.GatePassed = false;
				ep.AllowedTarget = null;
				ep.Lines = 0;
				HandReciever val = SafeHover(hand);
				bool flag3 = HandOccupancy.IsPendingGrab(hand);
				if (flag3)
				{
					ep.SawHover = true;
					ep.HoverName = Name(val);
				}
				Emit(ep, $"[diag {side}] grip-press grip={gripForce:F3} trigger={SafeTrigger(hand):F2} gameGrabbed={flag} attached={HandOccupancy.Attached(hand)} hover={(((Object)(object)val == (Object)null) ? "none" : Name(val))} committedHover={flag3}");
			}
			return;
		}
		if (gripForce > ep.PeakGrip)
		{
			ep.PeakGrip = gripForce;
		}
		if (!ep.SawHover)
		{
			HandReciever receiver = SafeHover(hand);
			if (HandOccupancy.IsPendingGrab(hand))
			{
				ep.SawHover = true;
				ep.HoverName = Name(receiver);
				Emit(ep, $"[diag {side}] +{Frames(ep)}f hover committed -> {ep.HoverName} (grip={gripForce:F3})");
			}
		}
		if (ep.GameGrabbedFrame < 0 && flag)
		{
			ep.GameGrabbedFrame = Time.frameCount;
			Emit(ep, $"[diag {side}] +{Frames(ep)}f Marrow grab threshold crossed (grip={gripForce:F3})");
		}
		if (ep.AttachedFrame < 0 && HandOccupancy.Attached(hand) != HandOccupancy.Reason.Free)
		{
			ep.AttachedFrame = Time.frameCount;
			Emit(ep, $"[diag {side}] +{Frames(ep)}f ATTACHED ({HandOccupancy.Attached(hand)})");
		}
		if (!flag2)
		{
			Close(ep, side);
		}
	}

	private static void Close(Episode ep, string side)
	{
		int num = ((ep.AttachedFrame < 0) ? (-1) : (ep.AttachedFrame - ep.StartFrame));
		float value = (Time.realtimeSinceStartup - ep.StartTime) * 1000f;
		Log.Msg($"[diag {side}] episode end: peakGrip={ep.PeakGrip:F3} attachDelay={((num < 0) ? "never" : (num + "f"))} window={value:F0}ms hover={ep.HoverName ?? "none"} gateBlocked={ep.Blocked} gatePassed={ep.GatePassed}" + ((ep.GatePassed && ep.AllowedTarget != null) ? (" target=" + ep.AllowedTarget) : ""));
		ep.Open = false;
	}

	private static void Abandon(Episode ep)
	{
		ep.Open = false;
	}

	private static int Frames(Episode ep)
	{
		return Time.frameCount - ep.StartFrame;
	}

	private static void Emit(Episode ep, string line)
	{
		if (ep.Lines < 6)
		{
			ep.Lines++;
			Log.Msg(line);
		}
	}

	internal static void NoteBlocked(Hand hand, HandOccupancy.Reason reason, Collider target)
	{
		Episode episode = EpisodeFor(hand);
		if (episode != null)
		{
			episode.Blocked = true;
		}
		if (Prefs.Diagnostics && Due(hand, ref _lastBlockedLogL, ref _lastBlockedLogR))
		{
			Log.Msg($"[diag {Side(hand)}] BLOCKED climb — {HandOccupancy.Describe(reason)}; would have anchored to '{Path(target)}'");
		}
	}

	internal static void NoteStaleGrip(Hand hand, Collider target)
	{
		Episode episode = EpisodeFor(hand);
		if (episode != null)
		{
			episode.Blocked = true;
		}
		if (Prefs.Diagnostics && Due(hand, ref _lastStaleGripLogL, ref _lastStaleGripLogR))
		{
			Log.Msg($"[diag {Side(hand)}] BLOCKED climb — {GripIntent.Describe(GripPressTracker.Verdict.Stale)}; would have anchored to '{Path(target)}'. Release grip and press again at the surface.");
		}
	}

	internal static void NoteAllowed(Hand hand, Collider target)
	{
		Episode episode = EpisodeFor(hand);
		if (episode != null && !episode.GatePassed)
		{
			episode.GatePassed = true;
			if (Prefs.Diagnostics)
			{
				episode.AllowedTarget = Path(target);
			}
		}
	}

	internal static void NoteWatchdog(Hand hand, HandOccupancy.Reason reason)
	{
		if (Due(hand, ref _lastWatchdogLogL, ref _lastWatchdogLogR))
		{
			Log.Warning($"[{Side(hand)}] tore down a climb point on an occupied hand ({HandOccupancy.Describe(reason)}).");
		}
	}

	private static Episode EpisodeFor(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 val = SideOf(hand);
		if ((int)val != 1)
		{
			if ((int)val == 2)
			{
				return RightEpisode;
			}
			return null;
		}
		return LeftEpisode;
	}

	private static string Side(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 val = SideOf(hand);
		if ((int)val != 1)
		{
			if ((int)val == 2)
			{
				return "R";
			}
			return "?";
		}
		return "L";
	}

	private static Handedness SideOf(Hand hand)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		return HandOccupancy.SideOf(hand);
	}

	private static HandReciever SafeHover(Hand hand)
	{
		try
		{
			return hand.HoveringReceiver;
		}
		catch
		{
			return null;
		}
	}

	private static float SafeTrigger(Hand hand)
	{
		try
		{
			return hand.GetIndexTriggerAxis();
		}
		catch
		{
			return -1f;
		}
	}

	private static string Name(HandReciever receiver)
	{
		try
		{
			return ((Object)(object)receiver == (Object)null) ? "none" : ((Object)receiver).name;
		}
		catch
		{
			return "?";
		}
	}

	private static string Path(Collider collider)
	{
		try
		{
			if ((Object)(object)collider == (Object)null)
			{
				return "none";
			}
			string text = ((Object)collider).name;
			Transform parent = ((Component)collider).transform.parent;
			for (int i = 0; i < 3; i++)
			{
				if (!((Object)(object)parent != (Object)null))
				{
					break;
				}
				text = ((Object)parent).name + "/" + text;
				parent = parent.parent;
			}
			return text;
		}
		catch
		{
			return "?";
		}
	}
}
internal static class GripIntent
{
	internal const float DefaultWindowMs = 80f;

	internal const float MaxWindowMs = 500f;

	private static float Window => GripPressTracker.ClampWindowMs(Prefs.GripPressWindowMs, 80f, 500f) / 1000f;

	internal static GripPressTracker.Verdict Authorize(Hand hand)
	{
		//IL_0001: Unknown result type (might be due to invalid IL or missing references)
		GripPressTracker gripPressTracker = GripWatch.TrackerFor(HandOccupancy.SideOf(hand));
		if (gripPressTracker == null)
		{
			return GripPressTracker.Verdict.Unknown;
		}
		GripPressTracker.Verdict num = gripPressTracker.Check(Time.frameCount, Time.realtimeSinceStartup, Window);
		if (num == GripPressTracker.Verdict.Fresh)
		{
			gripPressTracker.Consume();
		}
		return num;
	}

	internal static string Describe(GripPressTracker.Verdict verdict)
	{
		return verdict switch
		{
			GripPressTracker.Verdict.Fresh => "fresh grip press", 
			GripPressTracker.Verdict.Stale => "no fresh grip press (grip was already held before this hand reached the surface)", 
			_ => "grip state unreadable", 
		};
	}
}
internal sealed class GripPressTracker
{
	internal enum Phase
	{
		Unknown,
		Released,
		Held
	}

	internal enum Verdict
	{
		Fresh,
		Stale,
		Unknown
	}

	private Phase _phase;

	private float _pressedAt = -1f;

	private int _pressedFrame = -1;

	private float _heldSince = -1f;

	private int _consumedFrame = -1;

	internal Phase State => _phase;

	internal int EdgeFrame => _pressedFrame;

	internal float EdgeTime => _pressedAt;

	internal bool CanRead => _phase != Phase.Unknown;

	internal float HeldFor(float now)
	{
		if (!(_heldSince < 0f))
		{
			return now - _heldSince;
		}
		return -1f;
	}

	internal void Observe(bool readable, bool held, int frame, float now)
	{
		if (!readable)
		{
			_phase = Phase.Unknown;
			_heldSince = -1f;
			_pressedAt = -1f;
			_pressedFrame = -1;
			return;
		}
		if (!held)
		{
			_phase = Phase.Released;
			_heldSince = -1f;
			_pressedAt = -1f;
			_pressedFrame = -1;
			return;
		}
		if (_phase == Phase.Released)
		{
			_heldSince = now;
			_pressedAt = now;
			_pressedFrame = frame;
		}
		else if (_phase == Phase.Unknown)
		{
			_heldSince = now;
			_pressedAt = -1f;
			_pressedFrame = -1;
		}
		_phase = Phase.Held;
	}

	internal Verdict Check(int frame, float now, float window)
	{
		if (_phase == Phase.Unknown)
		{
			return Verdict.Unknown;
		}
		if (_pressedFrame < 0)
		{
			return Verdict.Stale;
		}
		if (_pressedFrame == _consumedFrame)
		{
			return Verdict.Stale;
		}
		if (frame == _pressedFrame)
		{
			return Verdict.Fresh;
		}
		float num = now - _pressedAt;
		if (!(num >= 0f) || !(num <= window))
		{
			return Verdict.Stale;
		}
		return Verdict.Fresh;
	}

	internal void Consume()
	{
		_consumedFrame = _pressedFrame;
	}

	internal static float ClampWindowMs(float ms, float defaultMs, float maxMs)
	{
		if (float.IsNaN(ms))
		{
			return defaultMs;
		}
		if (ms < 0f)
		{
			return 0f;
		}
		if (!(ms > maxMs))
		{
			return ms;
		}
		return maxMs;
	}
}
internal static class GripWatch
{
	internal const float PressThreshold = 0.01f;

	internal const float GrabWindow = 0.5f;

	private static readonly GripPressTracker Left = new GripPressTracker();

	private static readonly GripPressTracker Right = new GripPressTracker();

	private static int _leftSampledFrame = -1;

	private static int _rightSampledFrame = -1;

	internal static void Tick()
	{
		Sample((Handedness)1);
		Sample((Handedness)2);
	}

	internal static GripPressTracker TrackerFor(Handedness handedness)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		//IL_0002: Invalid comparison between Unknown and I4
		//IL_000a: 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_0012: Invalid comparison between Unknown and I4
		//IL_0004: Unknown result type (might be due to invalid IL or missing references)
		//IL_0006: Invalid comparison between Unknown and I4
		if ((int)handedness != 1 && (int)handedness != 2)
		{
			return null;
		}
		Sample(handedness);
		if ((int)handedness != 1)
		{
			return Right;
		}
		return Left;
	}

	internal static float HeldFor(Handedness handedness)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		return TrackerFor(handedness)?.HeldFor(Time.realtimeSinceStartup) ?? (-1f);
	}

	internal static bool WithinGrabWindow(Handedness handedness)
	{
		//IL_0000: Unknown result type (might be due to invalid IL or missing references)
		float num = HeldFor(handedness);
		if (num >= 0f)
		{
			return num <= 0.5f;
		}
		return false;
	}

	private static void Sample(Handedness handedness)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		//IL_0008: Invalid comparison between Unknown and I4
		//IL_0026: Unknown result type (might be due to invalid IL or missing references)
		//IL_0028: Invalid comparison between Unknown and I4
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		//IL_003e: Unknown result type (might be due to invalid IL or missing references)
		int frameCount = Time.frameCount;
		if ((int)handedness == 1)
		{
			if (_leftSampledFrame != frameCount)
			{
				_leftSampledFrame = frameCount;
				Feed(Left, handedness, frameCount);
			}
		}
		else if ((int)handedness == 2 && _rightSampledFrame != frameCount)
		{
			_rightSampledFrame = frameCount;
			Feed(Right, handedness, frameCount);
		}
	}

	private static void Feed(GripPressTracker tracker, Handedness handedness, int frame)
	{
		//IL_0006: Unknown result type (might be due to invalid IL or missing references)
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		BaseController val = Controller(handedness);
		if ((Object)(object)val == (Object)null)
		{
			tracker.Observe(readable: false, held: false, frame, realtimeSinceStartup);
			return;
		}
		float gripForce;
		try
		{
			gripForce = val.GetGripForce();
		}
		catch
		{
			tracker.Observe(readable: false, held: false, frame, realtimeSinceStartup);
			return;
		}
		tracker.Observe(readable: true, gripForce > 0.01f, frame, realtimeSinceStartup);
	}

	private static BaseController Controller(Handedness handedness)
	{
		//IL_000b: Unknown result type (might be due to invalid IL or missing references)
		//IL_000d: Invalid comparison between Unknown and I4
		try
		{
			if (!Player.ControllersExist)
			{
				return null;
			}
			return ((int)handedness == 1) ? Player.LeftController : Player.RightController;
		}
		catch
		{
			return null;
		}
	}
}
internal static class HandOccupancy
{
	internal enum Reason
	{
		Free,
		AttachedReceiver,
		AttachedObject,
		PendingGrab
	}

	internal static Reason Attached(Hand hand)
	{
		if ((Object)(object)hand == (Object)null)
		{
			return Reason.Free;
		}
		if ((Object)(object)hand.AttachedReceiver != (Object)null)
		{
			return Reason.AttachedReceiver;
		}
		if (hand.HasAttachedObject())
		{
			return Reason.AttachedObject;
		}
		return Reason.Free;
	}

	internal static Reason Evaluate(Hand hand)
	{
		//IL_0014: Unknown result type (might be due to invalid IL or missing references)
		Reason reason = Attached(hand);
		if (reason != Reason.Free)
		{
			return reason;
		}
		if (Prefs.BlockPendingGrab && GripWatch.WithinGrabWindow(SideOf(hand)) && IsPendingGrab(hand))
		{
			return Reason.PendingGrab;
		}
		return Reason.Free;
	}

	internal static bool IsPendingGrab(Hand hand)
	{
		if ((Object)(object)hand == (Object)null)
		{
			return false;
		}
		try
		{
			HandReciever hoveringReceiver = hand.HoveringReceiver;
			if ((Object)(object)hoveringReceiver == (Object)null)
			{
				return false;
			}
			if (!hoveringReceiver.HasHost)
			{
				return false;
			}
			if (hoveringReceiver.IsStatic)
			{
				return false;
			}
			if (!hoveringReceiver.HasRigidbody)
			{
				return false;
			}
			return IsWithinReach(hand, hoveringReceiver);
		}
		catch
		{
			return false;
		}
	}

	private static bool IsWithinReach(Hand hand, HandReciever receiver)
	{
		//IL_0023: 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)
		float num = hand.HoverSphereRadius;
		if (num <= 0f)
		{
			num = 0.1f;
		}
		float num2 = num * 4f;
		Vector3 val = ((Component)receiver).transform.position - ((Component)hand).transform.position;
		return ((Vector3)(ref val)).sqrMagnitude <= num2 * num2;
	}

	internal static Handedness SideOf(Hand hand)
	{
		//IL_0017: Unknown result type (might be due to invalid IL or missing references)
		//IL_000a: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: 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)
		try
		{
			return (Handedness)((!((Object)(object)hand == (Object)null)) ? ((int)hand.handedness) : 0);
		}
		catch
		{
			return (Handedness)0;
		}
	}

	internal static string Describe(Reason reason)
	{
		return reason switch
		{
			Reason.AttachedReceiver => "hand.AttachedReceiver", 
			Reason.AttachedObject => "hand.HasAttachedObject", 
			Reason.PendingGrab => "hand.HoveringReceiver (dynamic, rigidbody-hosted, in reach)", 
			_ => "free", 
		};
	}
}
internal static class Prefs
{
	private static MelonPreferences_Category _category;

	private static MelonPreferences_Entry<bool> _enabled;

	private static MelonPreferences_Entry<bool> _blockPendingGrab;

	private static MelonPreferences_Entry<bool> _requireGripPress;

	private static MelonPreferences_Entry<float> _gripPressWindowMs;

	private static MelonPreferences_Entry<bool> _diagnostics;

	internal static bool Enabled
	{
		get
		{
			return _enabled?.Value ?? true;
		}
		set
		{
			if (_enabled != null)
			{
				_enabled.Value = value;
				MelonPreferences_Category category = _category;
				if (category != null)
				{
					category.SaveToFile(false);
				}
			}
		}
	}

	internal static bool BlockPendingGrab
	{
		get
		{
			return _blockPendingGrab?.Value ?? true;
		}
		set
		{
			if (_blockPendingGrab != null)
			{
				_blockPendingGrab.Value = value;
				MelonPreferences_Category category = _category;
				if (category != null)
				{
					category.SaveToFile(false);
				}
			}
		}
	}

	internal static bool RequireGripPress
	{
		get
		{
			return _requireGripPress?.Value ?? true;
		}
		set
		{
			if (_requireGripPress != null)
			{
				_requireGripPress.Value = value;
				MelonPreferences_Category category = _category;
				if (category != null)
				{
					category.SaveToFile(false);
				}
			}
		}
	}

	internal static float GripPressWindowMs
	{
		get
		{
			return _gripPressWindowMs?.Value ?? 80f;
		}
		set
		{
			if (_gripPressWindowMs != null)
			{
				_gripPressWindowMs.Value = value;
				MelonPreferences_Category category = _category;
				if (category != null)
				{
					category.SaveToFile(false);
				}
			}
		}
	}

	internal static bool Diagnostics
	{
		get
		{
			return _diagnostics?.Value ?? false;
		}
		set
		{
			if (_diagnostics != null)
			{
				_diagnostics.Value = value;
				MelonPreferences_Category category = _category;
				if (category != null)
				{
					category.SaveToFile(false);
				}
			}
		}
	}

	internal static void Setup()
	{
		_category = MelonPreferences.CreateCategory("ClimbAnything Weapon Fix");
		_enabled = _category.CreateEntry<bool>("Enabled", true, "Enabled", "Block ClimbAnything from starting a climb on a hand that is already grabbing something. This covers the weapon-grip launch only; the sticky-hand fix has its own switch, and ClimbAnything is fully unpatched only when both are off.", false, false, (ValueValidator)null, (string)null);
		_blockPendingGrab = _category.CreateEntry<bool>("BlockPendingGrab", true, "Block climbs on a pending grab", "Also refuse a climb when Marrow's hover pass has already selected a physical grabbable for that hand. This is what closes the window between the grip leaving zero and the grab completing. Turn it off to gate on a completed attach only.", false, false, (ValueValidator)null, (string)null);
		_requireGripPress = _category.CreateEntry<bool>("RequireGripPress", true, "Require an intentional grip press", "Only let ClimbAnything start a climb on a hand that has just pressed grip. ClimbAnything otherwise treats a held grip as a standing request to climb and re-scans every frame, so a grip squeezed in open air makes the hand sticky and it attaches to whatever wall it later touches. Turn this off to restore ClimbAnything's original hold-to-climb behaviour.", false, false, (ValueValidator)null, (string)null);
		_gripPressWindowMs = _category.CreateEntry<float>("GripPressWindowMs", 80f, "Grip press window (ms)", "How long after pressing grip a climb may still start. Reaching for a wall and squeezing is one motion, so ClimbAnything can find the surface a frame or two after the press; this is the slack that covers it. Lower is stricter. 0 means the press and the surface must land on the same frame.", false, false, (ValueValidator)null, (string)null);
		_diagnostics = _category.CreateEntry<bool>("Diagnostics", false, "Diagnostic logging", "Log each grab attempt: grip force, trigger, hover state, and how many frames Marrow took to attach. Useful for comparing 'grip the weapon' against 'trigger first, then grip'.", false, false, (ValueValidator)null, (string)null);
	}
}
public class WeaponFixMod : MelonMod
{
	private static Instance _logger;

	public override void OnInitializeMelon()
	{
		_logger = ((MelonBase)this).LoggerInstance;
		try
		{
			Prefs.Setup();
		}
		catch (Exception ex)
		{
			Warn("Could not create preferences (" + ex.GetType().Name + "); running on defaults.");
		}
		ClimbAnythingBridge.Initialize(((MelonBase)this).HarmonyInstance);
		SetupMenu();
	}

	public override void OnLateInitializeMelon()
	{
		ClimbAnythingBridge.Retry(((MelonBase)this).HarmonyInstance);
	}

	public override void OnUpdate()
	{
		GripWatch.Tick();
		ClimbAnythingBridge.Watchdog();
		Diagnostics.Tick();
	}

	public override void OnFixedUpdate()
	{
		ClimbAnythingBridge.Watchdog();
	}

	private static void SetupMenu()
	{
		//IL_000a: 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_0051: Unknown result type (might be due to invalid IL or missing references)
		//IL_0086: Unknown result type (might be due to invalid IL or missing references)
		//IL_00bb: 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)
		try
		{
			Page obj = Page.Root.CreatePage("ClimbAnything Weapon Fix", Color.cyan, 0, true);
			obj.CreateBool("Enabled", Color.cyan, Prefs.Enabled, (Action<bool>)delegate(bool value)
			{
				Prefs.Enabled = value;
			});
			obj.CreateBool("Block pending grabs", Color.cyan, Prefs.BlockPendingGrab, (Action<bool>)delegate(bool value)
			{
				Prefs.BlockPendingGrab = value;
			});
			obj.CreateBool("Require grip press", Color.cyan, Prefs.RequireGripPress, (Action<bool>)delegate(bool value)
			{
				Prefs.RequireGripPress = value;
			});
			obj.CreateFloat("Grip press window (ms)", Color.cyan, Prefs.GripPressWindowMs, 10f, 0f, 500f, (Action<float>)delegate(float value)
			{
				Prefs.GripPressWindowMs = value;
			});
			obj.CreateBool("Diagnostic logging", Color.yellow, Prefs.Diagnostics, (Action<bool>)delegate(bool value)
			{
				Prefs.Diagnostics = value;
			});
		}
		catch (Exception ex)
		{
			Log.Warning("Could not build the BoneMenu page (" + ex.GetType().Name + "); preferences still work from the config file.");
		}
	}

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

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

	internal static void Warning(string message)
	{
		WeaponFixMod.Warn(message);
	}
}