Decompiled source of BreakFree v1.0.0

BreakFree.dll

Decompiled 12 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("0.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BreakFree
{
	[BepInPlugin("com.breakfree.repo.breakfree", "break free", "1.0.0")]
	public sealed class BreakFreePlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "com.breakfree.repo.breakfree";

		public const string PluginName = "break free";

		public const string PluginVersion = "1.0.0";

		private static ConfigEntry<bool>? enableDiagnostics;

		private Harmony? harmony;

		private bool isPatched;

		private void Awake()
		{
			enableDiagnostics = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "EnableDiagnostics", true, "Enable break free diagnostic logging.");
			PatchPlugin();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"break free 1.0.0 loaded");
		}

		public static void LogDiagnostic(string message)
		{
			ConfigEntry<bool>? obj = enableDiagnostics;
			if (obj == null || obj.Value)
			{
				Debug.Log((object)("[break free] " + message));
			}
		}

		private void OnEnable()
		{
			PatchPlugin();
		}

		private void OnDisable()
		{
			UnpatchPlugin();
		}

		private void OnDestroy()
		{
			UnpatchPlugin();
		}

		private void PatchPlugin()
		{
			//IL_0017: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			if (!isPatched)
			{
				if (harmony == null)
				{
					harmony = new Harmony("com.breakfree.repo.breakfree");
				}
				harmony.PatchAll();
				isPatched = true;
				LogDiagnostic("Harmony patches applied successfully");
			}
		}

		private void UnpatchPlugin()
		{
			if (isPatched || harmony != null)
			{
				Harmony? obj = harmony;
				if (obj != null)
				{
					obj.UnpatchSelf();
				}
				harmony = null;
				isPatched = false;
				PlayerTumbleUpdatePatch.ClearAllStates();
			}
		}
	}
	[HarmonyPatch(typeof(PlayerTumble), "Update")]
	internal static class PlayerTumbleUpdatePatch
	{
		private readonly struct PressState
		{
			public float LastJumpAt { get; }

			public float CooldownUntil { get; }

			public PressState(float lastJumpAt, float cooldownUntil)
			{
				LastJumpAt = lastJumpAt;
				CooldownUntil = cooldownUntil;
			}
		}

		private const float DoublePressWindowSeconds = 2f;

		private const float SuccessCooldownSeconds = 1f;

		private static readonly Dictionary<PlayerTumble, PressState> PressStates = new Dictionary<PlayerTumble, PressState>();

		private static readonly HashSet<PlayerTumble> InputTriggeredTumbles = new HashSet<PlayerTumble>();

		private static FieldRef<PlayerTumble, bool>? isTumblingRef;

		private static FieldRef<PlayerTumble, bool>? isPlayerInputTriggeredRef;

		private static FieldRef<PlayerTumble, PhysGrabObject>? physGrabObjectRef;

		private static FieldRef<PlayerAvatar, bool>? isLocalRef;

		private static bool fieldRefsInitialized;

		private static bool fieldRefsUnavailable;

		private static void Postfix(PlayerTumble __instance)
		{
			if (!EnsureFieldRefsInitialized())
			{
				return;
			}
			if (!TryGetBreakFreeContext(__instance, out PhysGrabObject physGrabObject))
			{
				if (IsTumblingActive(__instance))
				{
					ClearPressState(__instance);
				}
				else
				{
					ClearState(__instance);
				}
				return;
			}
			float time = Time.time;
			PressState state = GetState(__instance);
			if (state.CooldownUntil > time)
			{
				PressStates[__instance] = state;
			}
			else if (!SemiFunc.InputDown((InputKey)1))
			{
				PressStates[__instance] = state;
			}
			else if (state.LastJumpAt >= 0f && time - state.LastJumpAt <= 2f)
			{
				BreakFreePlugin.LogDiagnostic($"double jump detected / releasing grabbers; grabberCount={CountOtherPlayerGrabbers(physGrabObject, __instance.playerAvatar)}");
				ForceReleaseGrabbers(__instance, physGrabObject);
				RequestStandUp(__instance);
				PressStates[__instance] = new PressState(-1f, time + 1f);
			}
			else
			{
				BreakFreePlugin.LogDiagnostic($"first jump recorded; grabberCount={CountOtherPlayerGrabbers(physGrabObject, __instance.playerAvatar)}");
				PressStates[__instance] = new PressState(time, 0f);
			}
		}

		public static void ClearAllStates()
		{
			PressStates.Clear();
			InputTriggeredTumbles.Clear();
		}

		public static void TrackTumbleSetRpcInput(PlayerTumble? tumble, bool isTumbling, bool playerInput)
		{
			if (!((Object)(object)tumble == (Object)null))
			{
				bool flag = InputTriggeredTumbles.Contains(tumble);
				if (isTumbling && playerInput)
				{
					InputTriggeredTumbles.Add(tumble);
				}
				else
				{
					ClearState(tumble);
				}
				bool isLocal;
				bool flag2 = TryIsLocalTumble(tumble, out isLocal);
				bool flag3 = InputTriggeredTumbles.Contains(tumble);
				if (isLocal || (!flag2 && flag != flag3))
				{
					BreakFreePlugin.LogDiagnostic(string.Format("TumbleSetRPC observed: isTumbling={0}, playerInput={1}, tracked={2}, local={3}", isTumbling, playerInput, flag3, flag2 ? isLocal.ToString() : "unknown"));
				}
			}
		}

		private static bool EnsureFieldRefsInitialized()
		{
			if (fieldRefsInitialized)
			{
				return true;
			}
			if (fieldRefsUnavailable)
			{
				return false;
			}
			try
			{
				isTumblingRef = AccessTools.FieldRefAccess<PlayerTumble, bool>("isTumbling");
				isPlayerInputTriggeredRef = AccessTools.FieldRefAccess<PlayerTumble, bool>("isPlayerInputTriggered");
				physGrabObjectRef = AccessTools.FieldRefAccess<PlayerTumble, PhysGrabObject>("physGrabObject");
				isLocalRef = AccessTools.FieldRefAccess<PlayerAvatar, bool>("isLocal");
				fieldRefsInitialized = true;
				BreakFreePlugin.LogDiagnostic("FieldRef initialization succeeded");
				return true;
			}
			catch (Exception arg)
			{
				fieldRefsUnavailable = true;
				Debug.LogWarning((object)$"[break free] PlayerTumble patch disabled because field access initialization failed: {arg}");
				return false;
			}
		}

		private static bool TryGetBreakFreeContext(PlayerTumble? tumble, out PhysGrabObject? physGrabObject)
		{
			physGrabObject = null;
			FieldRef<PlayerTumble, bool> val = isTumblingRef;
			FieldRef<PlayerTumble, bool> val2 = isPlayerInputTriggeredRef;
			FieldRef<PlayerTumble, PhysGrabObject> val3 = physGrabObjectRef;
			FieldRef<PlayerAvatar, bool> val4 = isLocalRef;
			if (val == null || val2 == null || val3 == null || val4 == null)
			{
				return false;
			}
			if ((Object)(object)tumble == (Object)null)
			{
				return false;
			}
			PlayerAvatar playerAvatar = tumble.playerAvatar;
			if ((Object)(object)playerAvatar == (Object)null || !val4.Invoke(playerAvatar))
			{
				return false;
			}
			if (!val.Invoke(tumble) || (!val2.Invoke(tumble) && !IsTrackedInputTriggered(tumble)))
			{
				return false;
			}
			physGrabObject = val3.Invoke(tumble);
			if (physGrabObject?.playerGrabbing == null || physGrabObject.playerGrabbing.Count == 0)
			{
				return false;
			}
			PlayerAvatar localAvatar = playerAvatar;
			foreach (PhysGrabber item in physGrabObject.playerGrabbing)
			{
				if (IsOtherPlayerGrabber(item, localAvatar))
				{
					return true;
				}
			}
			return false;
		}

		private static void ForceReleaseGrabbers(PlayerTumble tumble, PhysGrabObject physGrabObject)
		{
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			PlayerAvatar playerAvatar = tumble.playerAvatar;
			if ((Object)(object)playerAvatar == (Object)null || physGrabObject.playerGrabbing == null)
			{
				return;
			}
			bool flag = IsMultiplayerSafe();
			PhysGrabber[] array = physGrabObject.playerGrabbing.ToArray();
			foreach (PhysGrabber val in array)
			{
				if (!IsOtherPlayerGrabber(val, playerAvatar))
				{
					continue;
				}
				try
				{
					if (flag && (Object)(object)val.photonView != (Object)null)
					{
						BreakFreePlugin.LogDiagnostic("releasing grabber via multiplayer RPC ReleaseObjectRPC(false, 1f, -1)");
						val.photonView.RPC("ReleaseObjectRPC", (RpcTarget)0, new object[3] { false, 1f, -1 });
					}
					else
					{
						BreakFreePlugin.LogDiagnostic("releasing grabber via fallback ReleaseObjectRPC(true, 1f, -1)");
						val.ReleaseObjectRPC(true, 1f, -1, default(PhotonMessageInfo));
					}
				}
				catch (Exception arg)
				{
					Debug.LogWarning((object)$"[break free] Failed to release grabber: {arg}");
				}
			}
		}

		private static void RequestStandUp(PlayerTumble? tumble)
		{
			if ((Object)(object)tumble == (Object)null)
			{
				return;
			}
			try
			{
				BreakFreePlugin.LogDiagnostic("requesting native tumble exit via TumbleRequest(false, true)");
				tumble.TumbleRequest(false, true);
			}
			catch (Exception arg)
			{
				Debug.LogWarning((object)$"[break free] Failed to request stand up after release: {arg}");
			}
		}

		private static bool IsOtherPlayerGrabber(PhysGrabber? grabber, PlayerAvatar localAvatar)
		{
			if ((Object)(object)grabber != (Object)null && (Object)(object)grabber.playerAvatar != (Object)null)
			{
				return (Object)(object)grabber.playerAvatar != (Object)(object)localAvatar;
			}
			return false;
		}

		private static int CountOtherPlayerGrabbers(PhysGrabObject physGrabObject, PlayerAvatar? localAvatar)
		{
			if (physGrabObject.playerGrabbing == null || (Object)(object)localAvatar == (Object)null)
			{
				return 0;
			}
			int num = 0;
			foreach (PhysGrabber item in physGrabObject.playerGrabbing)
			{
				if (IsOtherPlayerGrabber(item, localAvatar))
				{
					num++;
				}
			}
			return num;
		}

		private static bool IsMultiplayerSafe()
		{
			try
			{
				return SemiFunc.IsMultiplayer();
			}
			catch
			{
				return false;
			}
		}

		private static PressState GetState(PlayerTumble tumble)
		{
			if (!PressStates.TryGetValue(tumble, out var value))
			{
				return new PressState(-1f, 0f);
			}
			return value;
		}

		private static void ClearState(PlayerTumble? tumble)
		{
			if ((Object)(object)tumble != (Object)null)
			{
				PressStates.Remove(tumble);
				InputTriggeredTumbles.Remove(tumble);
			}
		}

		private static void ClearPressState(PlayerTumble? tumble)
		{
			if ((Object)(object)tumble != (Object)null)
			{
				PressStates.Remove(tumble);
			}
		}

		private static bool IsTumblingActive(PlayerTumble? tumble)
		{
			FieldRef<PlayerTumble, bool> val = isTumblingRef;
			if ((Object)(object)tumble != (Object)null && val != null)
			{
				return val.Invoke(tumble);
			}
			return false;
		}

		private static bool IsTrackedInputTriggered(PlayerTumble tumble)
		{
			return InputTriggeredTumbles.Contains(tumble);
		}

		private static bool TryIsLocalTumble(PlayerTumble tumble, out bool isLocal)
		{
			isLocal = false;
			if (!EnsureFieldRefsInitialized())
			{
				return false;
			}
			FieldRef<PlayerAvatar, bool> val = isLocalRef;
			PlayerAvatar playerAvatar = tumble.playerAvatar;
			if (val == null || (Object)(object)playerAvatar == (Object)null)
			{
				return false;
			}
			isLocal = val.Invoke(playerAvatar);
			return true;
		}
	}
	[HarmonyPatch(typeof(PlayerTumble), "TumbleSetRPC")]
	internal static class PlayerTumbleSetRpcPatch
	{
		private static void Postfix(PlayerTumble __instance, bool _isTumbling, bool _playerInput)
		{
			PlayerTumbleUpdatePatch.TrackTumbleSetRpcInput(__instance, _isTumbling, _playerInput);
		}
	}
}