Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of BreakFree v1.0.0
BreakFree.dll
Decompiled 12 hours agousing 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); } } }