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);
}
}