Decompiled source of Personal Grapple v0.11.1

PersonalGrapple.dll

Decompiled 9 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CG;
using CG.Space;
using Ectype;
using Gameplay.Perks;
using Gameplay.Tags;
using Gameplay.Utilities;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Opsive.UltimateCharacterController.Character;
using Opsive.UltimateCharacterController.Character.Abilities;
using ResourceAssets;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using UnityEngine.InputSystem.Utilities;
using VoidManager;
using VoidManager.CustomGUI;
using VoidManager.MPModChecks;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("PersonalGrapple")]
[assembly: AssemblyDescription("Gives the Scavenger grappling hook to every role, alongside their normal role ability.")]
[assembly: AssemblyCompany("Jack")]
[assembly: AssemblyProduct("PersonalGrapple")]
[assembly: AssemblyFileVersion("0.11.1.0")]
[assembly: AssemblyMetadata("ModCategories", "Mods, Mod_Session Required, Disables Progression")]
[assembly: AssemblyMetadata("MPType", "Session")]
[assembly: AssemblyMetadata("ProgressionFlag", "enabled")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: AssemblyVersion("0.11.1.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[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;
		}
	}
}
internal static class ModFlagPolicy
{
	internal const bool FlagsSession = true;

	internal const MultiplayerType MPType = (MultiplayerType)20;

	internal const SessionModificationEffect NetworkSpawnedEffect = (SessionModificationEffect)2;

	internal const SessionModificationEffect RequiredEffect = (SessionModificationEffect)94;

	internal const string BuildTag = "enabled";
}
internal static class MenuKit
{
	private static readonly Dictionary<string, bool> foldouts = new Dictionary<string, bool>();

	private static readonly HashSet<string> loggedFailures = new HashSet<string>();

	public static void Header(string text)
	{
		GUILayout.Space(10f);
		GUILayout.Label("<b>" + text + "</b>", Array.Empty<GUILayoutOption>());
	}

	public static void Rule()
	{
		//IL_0023: 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_0042: Unknown result type (might be due to invalid IL or missing references)
		//IL_0056: Unknown result type (might be due to invalid IL or missing references)
		GUILayout.Space(6f);
		Rect rect = GUILayoutUtility.GetRect(1f, 1f, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) });
		Color color = GUI.color;
		GUI.color = new Color(1f, 1f, 1f, 0.25f);
		GUI.DrawTexture(rect, (Texture)(object)Texture2D.whiteTexture);
		GUI.color = color;
		GUILayout.Space(6f);
	}

	public static bool Foldout(string id, string label)
	{
		foldouts.TryGetValue(id, out var value);
		if (GUILayout.Button((value ? "[-] " : "[+] ") + label, Array.Empty<GUILayoutOption>()))
		{
			value = !value;
			foldouts[id] = value;
		}
		return value;
	}

	public static void DevZone(string id, Action body)
	{
		if (!Configs.IsDebugMode)
		{
			return;
		}
		GUILayout.Space(12f);
		Rule();
		if (!Foldout(id + ".dev", "Developer tools"))
		{
			return;
		}
		GUILayout.BeginVertical(GUI.skin.box, Array.Empty<GUILayoutOption>());
		try
		{
			body();
		}
		catch (Exception ex)
		{
			GUILayout.Label("Dev section failed: " + ex.Message + " (see log)", Array.Empty<GUILayoutOption>());
			if (loggedFailures.Add(id))
			{
				Debug.LogError((object)("[MenuKit] dev zone '" + id + "' failed: " + ex));
			}
		}
		finally
		{
			GUILayout.EndVertical();
		}
	}
}
internal static class EmbeddedSprite
{
	internal static byte[] ReadBytes(Assembly asm, string name, string suffix = null)
	{
		string text = null;
		string[] manifestResourceNames = asm.GetManifestResourceNames();
		string[] array = manifestResourceNames;
		foreach (string text2 in array)
		{
			if (string.Equals(text2, name, StringComparison.OrdinalIgnoreCase))
			{
				text = text2;
				break;
			}
		}
		if (text == null && suffix != null)
		{
			array = manifestResourceNames;
			foreach (string text3 in array)
			{
				if (text3.EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
				{
					text = text3;
					break;
				}
			}
		}
		if (text == null)
		{
			return null;
		}
		using Stream stream = asm.GetManifestResourceStream(text);
		if (stream == null)
		{
			return null;
		}
		using MemoryStream memoryStream = new MemoryStream();
		stream.CopyTo(memoryStream);
		return memoryStream.ToArray();
	}

	internal static Texture2D Decode(byte[] bytes, string texName, FilterMode filter, TextureWrapMode wrap, bool mipChain = false, bool linear = false)
	{
		//IL_0007: Unknown result type (might be due to invalid IL or missing references)
		//IL_000c: Unknown result type (might be due to invalid IL or missing references)
		//IL_0013: Unknown result type (might be due to invalid IL or missing references)
		//IL_0014: 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_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0022: Expected O, but got Unknown
		Texture2D val = new Texture2D(2, 2, (TextureFormat)4, mipChain, linear)
		{
			name = texName,
			filterMode = filter,
			wrapMode = wrap
		};
		if (!ImageConversion.LoadImage(val, bytes, false))
		{
			Object.Destroy((Object)(object)val);
			return null;
		}
		return val;
	}

	internal static Sprite ToSprite(Texture2D tex, string spriteName, Vector2 pivot, float pixelsPerUnit)
	{
		//IL_0019: Unknown result type (might be due to invalid IL or missing references)
		//IL_001e: Unknown result type (might be due to invalid IL or missing references)
		Sprite obj = Sprite.Create(tex, new Rect(0f, 0f, (float)((Texture)tex).width, (float)((Texture)tex).height), pivot, pixelsPerUnit);
		((Object)obj).name = spriteName;
		return obj;
	}
}
namespace PersonalGrapple
{
	[HarmonyPatch]
	internal static class AbilityIndexGuard
	{
		private static FieldInfo _locomotionF;

		private static readonly HashSet<long> Reported = new HashSet<long>();

		private static IEnumerable<MethodBase> TargetMethods()
		{
			List<MethodBase> list = new List<MethodBase>();
			try
			{
				Type type = AccessTools.TypeByName("Opsive.UltimateCharacterController.AddOns.Multiplayer.PhotonPun.Character.PunCharacter");
				Type type2 = AccessTools.TypeByName("CustomPunCharacter");
				if (type == null)
				{
					BepinPlugin.Log.LogError((object)"Grapple: PunCharacter not found; ability-index guard not installed (Opsive update?).");
					return list;
				}
				_locomotionF = AccessTools.Field(type, "m_CharacterLocomotion");
				if (_locomotionF == null)
				{
					BepinPlugin.Log.LogError((object)"Grapple: PunCharacter.m_CharacterLocomotion not found; ability-index guard not installed (Opsive update?).");
					return list;
				}
				Add(list, type, "OnAbilityActiveRPC");
				Add(list, type, "StartAbilityRPC");
				if (type2 != null)
				{
					Add(list, type2, "OnExtendedStartAbilityActiveRPC");
					Add(list, type2, "ExtendedStartAbilityRPC");
				}
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: resolving the ability-index guard's targets failed: " + ex));
				list.Clear();
			}
			return list;
		}

		private static void Add(List<MethodBase> into, Type type, string name)
		{
			MethodInfo methodInfo = AccessTools.Method(type, name, (Type[])null, (Type[])null);
			if (methodInfo != null)
			{
				into.Add(methodInfo);
				return;
			}
			BepinPlugin.Log.LogWarning((object)("Grapple: " + type.Name + "." + name + " not found; that ability RPC is unguarded (game or Opsive update?)."));
		}

		private static bool Prefix(object __instance, int abilityIndex, MethodBase __originalMethod)
		{
			try
			{
				object? value = _locomotionF.GetValue(__instance);
				UltimateCharacterLocomotion val = (UltimateCharacterLocomotion)((value is UltimateCharacterLocomotion) ? value : null);
				Ability[] array = (((Object)(object)val != (Object)null) ? val.Abilities : null);
				if (array == null)
				{
					return true;
				}
				if (abilityIndex >= 0 && abilityIndex < array.Length)
				{
					return true;
				}
				Report(__originalMethod.Name, abilityIndex, array.Length);
				return false;
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: ability-index guard failed: " + ex));
				return true;
			}
		}

		private static void Report(string rpc, int index, int length)
		{
			try
			{
				long item = ((long)index << 32) ^ (uint)length;
				if (Reported.Add(item))
				{
					BepinPlugin.Log.LogWarning((object)("Grapple: dropped " + rpc + " for ability index " + index + " on a character with " + length + " abilities. A crewmate is on a different mod build. Logged once per index/length pair."));
				}
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: ability-index report failed: " + ex));
			}
		}
	}
	internal static class GrappleClone
	{
		private sealed class Entry
		{
			public PrivateGrapplingHook Hook;

			public UltimateCharacterLocomotion Loco;

			public ChannelGrapplingHook Donor;

			public GrapplingHookFX Fx;

			public ChannelGrapplingHook FxOriginalAbility;

			public bool FxRepointed;

			public bool FxMissingLogged;

			public bool Mine;
		}

		private static readonly FieldInfo LocomotionF = AccessTools.Field(typeof(Ability), "m_CharacterLocomotion");

		private static readonly FieldInfo IndexF = AccessTools.Field(typeof(Ability), "m_Index");

		private static readonly FieldInfo ActiveIndexF = AccessTools.Field(typeof(Ability), "m_ActiveIndex");

		private static readonly FieldInfo InteractAbilityF = AccessTools.Field(typeof(ChannelGrapplingHook), "interactAbility");

		private static readonly FieldInfo IsSelectedF = AccessTools.Field(typeof(ClassAbility), "IsSelectedAbility");

		private static readonly MethodInfo EnableInputM = AccessTools.Method(typeof(ClassAbility), "EnableInput", (Type[])null, (Type[])null);

		private static readonly MethodInfo DisableInputM = AccessTools.Method(typeof(ClassAbility), "DisableInput", (Type[])null, (Type[])null);

		private static readonly MethodInfo ActivationKeyPressedM = AccessTools.Method(typeof(ChannelGrapplingHook), "ActivationKeyPressed", (Type[])null, (Type[])null);

		private static readonly MethodInfo ActivationKeyLetGoM = AccessTools.Method(typeof(ChannelGrapplingHook), "ActivationKeyLetGo", (Type[])null, (Type[])null);

		private static readonly MethodInfo AbilityUsableGetter = AccessTools.PropertyGetter(typeof(ClassAbility), "AbilityUsable");

		private static readonly FieldInfo FxAbilityF = AccessTools.Field(typeof(GrapplingHookFX), "Ability");

		private static readonly MethodInfo FxSubscribeM = AccessTools.Method(typeof(GrapplingHookFX), "SubscribeToAbility", (Type[])null, (Type[])null);

		private static readonly MethodInfo FxUnsubscribeM = AccessTools.Method(typeof(GrapplingHookFX), "UnsubscribeToAbility", (Type[])null, (Type[])null);

		private static readonly Dictionary<UltimateCharacterLocomotion, Entry> Entries = new Dictionary<UltimateCharacterLocomotion, Entry>();

		private static Entry _local;

		private static readonly HashSet<UltimateCharacterLocomotion> FailedOn = new HashSet<UltimateCharacterLocomotion>();

		private static bool _locoReadFailLogged;

		internal const string SyncKey = "personal-grapple:hook";

		private const float PRESS_LOG_INTERVAL = 2f;

		private static float _lastPressLog = -999f;

		internal static ChannelGrapplingHook Instance
		{
			get
			{
				if (_local == null)
				{
					return null;
				}
				return (ChannelGrapplingHook)(object)_local.Hook;
			}
		}

		internal static bool IsLive
		{
			get
			{
				if (Instance != null)
				{
					return (Object)(object)((ClassAbility)Instance).LocalPlayer != (Object)null;
				}
				return false;
			}
		}

		internal static bool IsSelected(ClassAbility ability)
		{
			if (ability != null && IsSelectedF != null)
			{
				return (bool)IsSelectedF.GetValue(ability);
			}
			return false;
		}

		internal static bool IsOurs(ClassAbility ability)
		{
			return ability is PrivateGrapplingHook;
		}

		internal static UltimateCharacterLocomotion LocomotionOf(Ability ability)
		{
			if (ability == null || !(LocomotionF != null))
			{
				return null;
			}
			object? value = LocomotionF.GetValue(ability);
			return (UltimateCharacterLocomotion)((value is UltimateCharacterLocomotion) ? value : null);
		}

		internal static bool Install(ChannelGrapplingHook donor)
		{
			if (donor == null)
			{
				return false;
			}
			UltimateCharacterLocomotion val = (UltimateCharacterLocomotion)((LocomotionF != null) ? /*isinst with value type is only supported in some contexts*/: null);
			if ((Object)(object)val == (Object)null)
			{
				if (!_locoReadFailLogged)
				{
					_locoReadFailLogged = true;
					BepinPlugin.Log.LogError((object)"Grapple: could not read m_CharacterLocomotion off a prefab hook (Opsive update?); that character gets no hook.");
				}
				return false;
			}
			if (FailedOn.Contains(val))
			{
				return false;
			}
			if (Entries.TryGetValue(val, out var value))
			{
				if (value.Donor == donor)
				{
					return true;
				}
				Uninstall(val);
			}
			bool flag = (Object)(object)((ClassAbility)donor).LocalPlayer != (Object)null && ((AbstractCloneStarObject)((ClassAbility)donor).LocalPlayer).IsMine;
			PrivateGrapplingHook privateGrapplingHook;
			try
			{
				privateGrapplingHook = PrivateGrapplingHook.Create(donor, val);
			}
			catch (Exception e)
			{
				Fail(val, flag, "building the hook copy failed", e);
				return false;
			}
			IndexF.SetValue(privateGrapplingHook, -1);
			if (ActiveIndexF != null)
			{
				ActiveIndexF.SetValue(privateGrapplingHook, -1);
			}
			Ability[] array = (Ability[])(((object)val.Abilities) ?? ((object)new Ability[0]));
			Ability[] array2 = (Ability[])(object)new Ability[array.Length + 1];
			Array.Copy(array, array2, array.Length);
			array2[array.Length] = (Ability)(object)privateGrapplingHook;
			Entry entry = new Entry
			{
				Hook = privateGrapplingHook,
				Loco = val,
				Donor = donor,
				Mine = flag
			};
			Entries[val] = entry;
			if (flag)
			{
				_local = entry;
			}
			try
			{
				val.Abilities = array2;
			}
			catch (Exception e2)
			{
				Entries.Remove(val);
				if (_local == entry)
				{
					_local = null;
				}
				try
				{
					val.Abilities = array;
				}
				catch (Exception ex)
				{
					BepinPlugin.Log.LogError((object)("Grapple: restoring the character's ability array failed: " + ex));
				}
				Fail(val, flag, "adding the hook copy to the locomotion failed", e2);
				return false;
			}
			try
			{
				FinishStart((ChannelGrapplingHook)(object)privateGrapplingHook, val, flag);
				RuntimeAbilities.Register((Ability)(object)privateGrapplingHook, "personal-grapple:hook");
				if (flag)
				{
					RepointFx(entry);
				}
			}
			catch (Exception e3)
			{
				Uninstall(val);
				Fail(val, flag, "post-install wiring failed", e3);
				return false;
			}
			if (BepinPlugin.Verbose)
			{
				BepinPlugin.Log.LogInfo((object)("Grapple: hook installed in locomotion slot " + array.Length + (flag ? " on the local character." : " on a crewmate's replica.")));
			}
			return true;
		}

		private static void FinishStart(ChannelGrapplingHook clone, UltimateCharacterLocomotion loco, bool mine)
		{
			if (InteractAbilityF != null)
			{
				InteractAbilityF.SetValue(clone, loco.GetAbility<CarryableInteract>(-1));
			}
			((Ability)clone).Enabled = true;
			if (mine && BepinPlugin.UseSharedAbilityKey.Value && EnableInputM != null)
			{
				EnableInputM.Invoke(clone, null);
			}
		}

		private static void RepointFx(Entry entry)
		{
			if (entry.FxRepointed)
			{
				return;
			}
			if ((Object)(object)entry.Fx == (Object)null)
			{
				entry.Fx = ((Component)entry.Loco).GetComponentInChildren<GrapplingHookFX>(true);
			}
			if ((Object)(object)entry.Fx == (Object)null)
			{
				if (!entry.FxMissingLogged)
				{
					entry.FxMissingLogged = true;
					BepinPlugin.Log.LogWarning((object)"Grapple: no GrapplingHookFX on the character; hook works with no rope or sound (game update?).");
				}
				return;
			}
			if (FxAbilityF == null || FxSubscribeM == null || FxUnsubscribeM == null)
			{
				entry.Fx = null;
				if (!entry.FxMissingLogged)
				{
					entry.FxMissingLogged = true;
					BepinPlugin.Log.LogWarning((object)"Grapple: GrapplingHookFX members not found; hook works with no rope or sound (game update?).");
				}
				return;
			}
			object? value = FxAbilityF.GetValue(entry.Fx);
			ChannelGrapplingHook val = (ChannelGrapplingHook)((value is ChannelGrapplingHook) ? value : null);
			if (val != null)
			{
				entry.FxOriginalAbility = val;
				FxUnsubscribeM.Invoke(entry.Fx, null);
				FxAbilityF.SetValue(entry.Fx, entry.Hook);
				FxSubscribeM.Invoke(entry.Fx, null);
				entry.FxRepointed = true;
			}
		}

		private static void RestoreFx(Entry entry)
		{
			if ((Object)(object)entry.Fx == (Object)null || !entry.FxRepointed)
			{
				return;
			}
			try
			{
				FxUnsubscribeM.Invoke(entry.Fx, null);
				FxAbilityF.SetValue(entry.Fx, entry.FxOriginalAbility);
				if (entry.FxOriginalAbility != null && (!entry.Mine || IsSelected((ClassAbility)(object)entry.FxOriginalAbility)))
				{
					FxSubscribeM.Invoke(entry.Fx, null);
				}
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: restoring the FX binding failed: " + ex));
			}
			entry.FxRepointed = false;
			entry.FxOriginalAbility = null;
		}

		internal static void OnCloneStarting(PrivateGrapplingHook hook)
		{
			try
			{
				Entry entry = FindByHook(hook);
				if (entry != null)
				{
					RepointFx(entry);
				}
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: FX borrow on start failed: " + ex));
			}
		}

		internal static void OnCloneStopped(PrivateGrapplingHook hook)
		{
			try
			{
				Entry entry = FindByHook(hook);
				if (entry != null && !entry.Mine)
				{
					RestoreFx(entry);
				}
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: FX return on stop failed: " + ex));
			}
		}

		private static Entry FindByHook(PrivateGrapplingHook hook)
		{
			foreach (Entry value in Entries.Values)
			{
				if (value.Hook == hook)
				{
					return value;
				}
			}
			return null;
		}

		internal static void Uninstall(UltimateCharacterLocomotion loco)
		{
			if ((Object)(object)loco == (Object)null || !Entries.TryGetValue(loco, out var value))
			{
				return;
			}
			Entries.Remove(loco);
			if (_local == value)
			{
				_local = null;
			}
			PrivateGrapplingHook hook = value.Hook;
			try
			{
				RuntimeAbilities.Unregister((Ability)(object)hook);
				RestoreFx(value);
				if (((Ability)hook).IsActive)
				{
					((Ability)hook).StopAbility(true);
				}
				((Ability)hook).Enabled = false;
				if (value.Mine && DisableInputM != null)
				{
					DisableInputM.Invoke(hook, null);
				}
				if ((Object)(object)((ClassAbility)hook).LocalPlayer != (Object)null && ((ClassAbility)hook).Stats != null)
				{
					((OrbitObject)((ClassAbility)hook).LocalPlayer).Stats.UnregisterChildCollection(((ClassAbility)hook).Stats);
				}
				if (loco.Abilities != null)
				{
					List<Ability> list = new List<Ability>(loco.Abilities.Length);
					Ability[] abilities = loco.Abilities;
					foreach (Ability val in abilities)
					{
						if ((object)val != hook)
						{
							list.Add(val);
						}
					}
					if (list.Count != loco.Abilities.Length)
					{
						loco.Abilities = list.ToArray();
					}
				}
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: uninstalling the hook copy failed: " + ex));
			}
			if (BepinPlugin.Verbose)
			{
				BepinPlugin.Log.LogInfo((object)("Grapple: hook uninstalled" + (value.Mine ? "." : " from a crewmate's replica.")));
			}
		}

		internal static void OnCharacterGone(ClassAbility ability)
		{
			FailedOn.RemoveWhere((UltimateCharacterLocomotion l) => (Object)(object)l == (Object)null);
			foreach (KeyValuePair<UltimateCharacterLocomotion, Entry> entry in Entries)
			{
				if ((object)entry.Value.Donor == ability)
				{
					Entry value = entry.Value;
					Entries.Remove(entry.Key);
					if (_local == value)
					{
						_local = null;
					}
					try
					{
						RuntimeAbilities.Unregister((Ability)(object)value.Hook);
						break;
					}
					catch (Exception ex)
					{
						BepinPlugin.Log.LogError((object)("Grapple: unregistering a torn-down hook failed: " + ex));
						break;
					}
				}
			}
		}

		private static bool PressLogAllowed()
		{
			float unscaledTime = Time.unscaledTime;
			if (unscaledTime - _lastPressLog < 2f)
			{
				return false;
			}
			_lastPressLog = unscaledTime;
			return true;
		}

		internal static void Press()
		{
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (!IsLive)
				{
					if (PressLogAllowed())
					{
						BepinPlugin.Log.LogWarning((object)("Grapple: key pressed with no live hook (installed=" + (Instance != null) + ", character alive=" + (Instance != null && (Object)(object)((ClassAbility)Instance).LocalPlayer != (Object)null) + ")."));
					}
				}
				else if (!(ActivationKeyPressedM == null) && !(Instance is PrivateGrapplingHook { Seated: not false }))
				{
					ActivationKeyPressedM.Invoke(Instance, new object[1] { (object)default(CallbackContext) });
					if (!((Ability)Instance).IsActive)
					{
						ReportBlockedStart();
					}
				}
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: press failed: " + ex));
			}
		}

		private static void ReportBlockedStart()
		{
			try
			{
				ChannelGrapplingHook instance = Instance;
				if ((((ClassAbility)instance).IsOnCooldown() && !BepinPlugin.Verbose) || !PressLogAllowed())
				{
					return;
				}
				StringBuilder stringBuilder = new StringBuilder("Grapple: key pressed but the hook did not start. ");
				stringBuilder.Append("Enabled=").Append(((Ability)instance).Enabled);
				stringBuilder.Append(" IsActive=").Append(((Ability)instance).IsActive);
				stringBuilder.Append(" Index=").Append(((Ability)instance).Index);
				stringBuilder.Append(" CanStartAbility=").Append(((Ability)instance).CanStartAbility());
				stringBuilder.Append(" AbilityUsable=").Append((AbilityUsableGetter != null) ? AbilityUsableGetter.Invoke(instance, null).ToString() : "?");
				stringBuilder.Append(" IsOnCooldown=").Append(((ClassAbility)instance).IsOnCooldown());
				stringBuilder.Append(" CurrentCooldown=").Append(((ClassAbility)instance).CurrentCooldown.ToString("F2"));
				stringBuilder.Append(" IsMine=").Append((Object)(object)((ClassAbility)instance).LocalPlayer != (Object)null && ((AbstractCloneStarObject)((ClassAbility)instance).LocalPlayer).IsMine);
				ChannelGrapplingHook val = ((_local != null) ? _local.Donor : null);
				if (val != null)
				{
					stringBuilder.Append(" | donor: Enabled=").Append(((Ability)val).Enabled);
					stringBuilder.Append(" AbilityUsable=").Append((AbilityUsableGetter != null) ? AbilityUsableGetter.Invoke(val, null).ToString() : "?");
					stringBuilder.Append(" IsSelected=").Append(IsSelected((ClassAbility)(object)val));
				}
				UltimateCharacterLocomotion val2 = ((_local != null) ? _local.Loco : null);
				if ((Object)(object)val2 != (Object)null)
				{
					Ability[] activeAbilities = val2.ActiveAbilities;
					int activeAbilityCount = val2.ActiveAbilityCount;
					stringBuilder.Append(" | active abilities (").Append(activeAbilityCount).Append("):");
					int num = 0;
					while (activeAbilities != null && num < activeAbilityCount && num < activeAbilities.Length)
					{
						Ability val3 = activeAbilities[num];
						if (val3 != null)
						{
							stringBuilder.Append(' ').Append(((object)val3).GetType().Name).Append("[i=")
								.Append(val3.Index);
							if (val3.ShouldBlockAbilityStart((Ability)(object)instance))
							{
								stringBuilder.Append(",BLOCKS");
							}
							if (val3.Index < ((Ability)instance).Index)
							{
								stringBuilder.Append(",outranks");
							}
							stringBuilder.Append(']');
						}
						num++;
					}
				}
				BepinPlugin.Log.LogWarning((object)stringBuilder.ToString());
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: start diagnostic failed: " + ex));
			}
		}

		internal static void Release()
		{
			//IL_002a: 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)
			try
			{
				if (IsLive && !(ActivationKeyLetGoM == null))
				{
					ActivationKeyLetGoM.Invoke(Instance, new object[1] { (object)default(CallbackContext) });
				}
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: release failed: " + ex));
			}
		}

		private static void Fail(UltimateCharacterLocomotion loco, bool mine, string what, Exception e)
		{
			if ((Object)(object)loco != (Object)null)
			{
				FailedOn.Add(loco);
			}
			BepinPlugin.Log.LogError((object)("Grapple: " + what + "; no hook on " + (mine ? "the local character" : "this crewmate's replica") + " until it respawns. " + ((e != null) ? e.ToString() : string.Empty)));
		}
	}
	internal static class GrappleHudButton
	{
		private const string Id = "jack.voidcrew.personalgrapple.grapple";

		private static Handle handle;

		internal static void Register()
		{
			Reregister();
			BepinPlugin.GrappleIconColor.SettingChanged += delegate
			{
				Reregister();
			};
		}

		private static void Reregister()
		{
			//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_0021: 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_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Expected O, but got Unknown
			if (handle != null)
			{
				handle.Dispose();
			}
			handle = HudAbilityBar.Register(new HudButtonSpec
			{
				Id = "jack.voidcrew.personalgrapple.grapple",
				Glyph = GrappleIcon.GetTexture(),
				ReadyColor = ParseColor(BepinPlugin.GrappleIconColor.Value),
				Key = () => GrappleKeyDriver.CurrentKey,
				Fill = Fill,
				Visible = () => BepinPlugin.ShowGrappleHudIcon.Value && GrappleLifecycle.Active
			});
		}

		private static float Fill()
		{
			ChannelGrapplingHook instance = GrappleClone.Instance;
			if (instance == null)
			{
				return 0f;
			}
			if (((FiniteDurationClassAbility)instance).IsOngoing())
			{
				float resolvedTimeDuration = ((FiniteDurationClassAbility)instance).ResolvedTimeDuration;
				if (!(resolvedTimeDuration > 0f))
				{
					return 0f;
				}
				return ((FiniteDurationClassAbility)instance).CurrentActiveDuration / resolvedTimeDuration;
			}
			if (((ClassAbility)instance).IsOnCooldown())
			{
				float value = ((ModifiablePrimitive<float, FloatModifier>)(object)((ClassAbility)instance).CooldownSeconds).Value;
				if (!(value > 0f))
				{
					return 0f;
				}
				return ((ClassAbility)instance).CurrentCooldown / value;
			}
			return 0f;
		}

		private static Color? ParseColor(string hex)
		{
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			if (string.IsNullOrEmpty(hex))
			{
				return null;
			}
			if (!hex.StartsWith("#"))
			{
				hex = "#" + hex;
			}
			Color value = default(Color);
			if (ColorUtility.TryParseHtmlString(hex, ref value))
			{
				return value;
			}
			BepinPlugin.Log.LogWarning((object)("Grapple: could not parse GrappleIconColor '" + hex + "', keeping the game's colour."));
			return null;
		}
	}
	internal static class GrappleIcon
	{
		private const int FallbackSize = 64;

		private const string ResourceFile = "grapple-hook-icon-square.png";

		private static Texture2D cached;

		private static bool buildFailed;

		internal static Texture2D GetTexture()
		{
			if ((Object)(object)cached != (Object)null || buildFailed)
			{
				return cached;
			}
			try
			{
				cached = LoadPackaged() ?? BuildFallback();
			}
			catch (Exception ex)
			{
				buildFailed = true;
				BepinPlugin.Log.LogWarning((object)("GrappleHud: icon load failed; no icon shown: " + ex));
			}
			return cached;
		}

		private static Texture2D LoadPackaged()
		{
			byte[] array = EmbeddedSprite.ReadBytes(Assembly.GetExecutingAssembly(), null, "grapple-hook-icon-square.png");
			if (array == null)
			{
				BepinPlugin.Log.LogWarning((object)"GrappleHud: embedded icon not found; showing a plain square.");
				return null;
			}
			Texture2D obj = EmbeddedSprite.Decode(array, "GrappleHudIconSquare", (FilterMode)1, (TextureWrapMode)1);
			if ((Object)(object)obj == (Object)null)
			{
				BepinPlugin.Log.LogWarning((object)"GrappleHud: embedded icon failed to decode; showing a plain square.");
			}
			return obj;
		}

		private static Texture2D BuildFallback()
		{
			//IL_0006: 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_0016: 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_0025: Expected O, but got Unknown
			//IL_0085: 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_008a: Unknown result type (might be due to invalid IL or missing references)
			Texture2D val = new Texture2D(64, 64, (TextureFormat)4, false)
			{
				name = "GrappleHudIconSquareFallback",
				filterMode = (FilterMode)1,
				wrapMode = (TextureWrapMode)1
			};
			Color32[] array = (Color32[])(object)new Color32[4096];
			for (int i = 0; i < 64; i++)
			{
				for (int j = 0; j < 64; j++)
				{
					bool flag = j >= 6 && j < 58 && i >= 6 && i < 58;
					array[i * 64 + j] = (flag ? new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)220) : new Color32(byte.MaxValue, byte.MaxValue, byte.MaxValue, (byte)0));
				}
			}
			val.SetPixels32(array);
			val.Apply(false, false);
			return val;
		}
	}
	internal class GrappleKeyDriver : MonoBehaviour
	{
		internal static bool AwaitingRebind;

		private Key key = (Key)42;

		private bool held;

		private bool updateFailLogged;

		internal static Key CurrentKey { get; private set; } = (Key)42;

		internal static string CurrentKeyDisplay { get; private set; } = "2";

		private unsafe static string DisplayNameFor(Key k)
		{
			string text = ((object)(*(Key*)(&k))/*cast due to .constrained prefix*/).ToString();
			if (text.StartsWith("Digit"))
			{
				return text.Substring(5);
			}
			if (text.StartsWith("Numpad"))
			{
				return "N" + text.Substring(6);
			}
			return text.ToUpperInvariant();
		}

		private void Start()
		{
			ParseKey();
			BepinPlugin.GrappleKey.SettingChanged += delegate
			{
				ParseKey();
			};
		}

		internal static bool TryParseKey(string text, out Key parsed)
		{
			parsed = (Key)0;
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			text = text.Trim();
			if (Enum.TryParse<Key>(text, ignoreCase: true, out parsed) && parsed)
			{
				return true;
			}
			if (text.Length == 1 && text[0] >= '0' && text[0] <= '9' && Enum.TryParse<Key>("Digit" + text, ignoreCase: true, out parsed))
			{
				return true;
			}
			return false;
		}

		private void ParseKey()
		{
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: 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_001a: 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_0036: Unknown result type (might be due to invalid IL or missing references)
			if (TryParseKey(BepinPlugin.GrappleKey.Value, out var parsed))
			{
				key = parsed;
				CurrentKey = parsed;
				CurrentKeyDisplay = DisplayNameFor(parsed);
				BepinPlugin.Log.LogInfo((object)$"Grapple key set to {key}.");
			}
			else
			{
				BepinPlugin.Log.LogWarning((object)$"Could not parse GrappleKey '{BepinPlugin.GrappleKey.Value}', keeping {key}.");
			}
		}

		private void Update()
		{
			try
			{
				UpdateInner();
			}
			catch (Exception ex)
			{
				if (!updateFailLogged)
				{
					updateFailLogged = true;
					BepinPlugin.Log.LogError((object)("Grapple: key driver update failed (logged once): " + ex));
				}
			}
		}

		private void UpdateInner()
		{
			//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_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_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_0045: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Invalid comparison between Unknown and I4
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			Keyboard current = Keyboard.current;
			if (AwaitingRebind && current != null)
			{
				Enumerator<KeyControl> enumerator = current.allKeys.GetEnumerator();
				try
				{
					while (enumerator.MoveNext())
					{
						KeyControl current2 = enumerator.Current;
						if (current2 != null && ((ButtonControl)current2).wasPressedThisFrame)
						{
							AwaitingRebind = false;
							if ((int)current2.keyCode != 60)
							{
								BepinPlugin.GrappleKey.Value = ((object)current2.keyCode/*cast due to .constrained prefix*/).ToString();
								BepinPlugin.Log.LogInfo((object)$"Grapple key rebound to {current2.keyCode}.");
							}
							break;
						}
					}
					return;
				}
				finally
				{
					((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
				}
			}
			if (!GrappleLifecycle.Active)
			{
				held = false;
			}
			else
			{
				if (current == null)
				{
					return;
				}
				KeyControl val = current[key];
				if (val != null)
				{
					if (((ButtonControl)val).wasPressedThisFrame)
					{
						held = true;
						GrappleClone.Press();
					}
					if (held && ((ButtonControl)val).wasReleasedThisFrame)
					{
						held = false;
						GrappleClone.Release();
					}
				}
			}
		}
	}
	internal static class GrappleLifecycle
	{
		internal static ChannelGrapplingHook LocalDonor { get; private set; }

		internal static bool HookIsNative { get; private set; }

		internal static bool Active
		{
			get
			{
				if (!HookIsNative && VoidManagerPlugin.Enabled)
				{
					return GrappleClone.IsLive;
				}
				return false;
			}
		}

		internal static void Serve(ClassAbility ability)
		{
			try
			{
				ServeInner(ability);
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: serve failed: " + ex));
			}
		}

		private static void ServeInner(ClassAbility ability)
		{
			ChannelGrapplingHook val = (ChannelGrapplingHook)(object)((ability is ChannelGrapplingHook) ? ability : null);
			if (val == null || GrappleClone.IsOurs((ClassAbility)(object)val) || (Object)(object)((ClassAbility)val).LocalPlayer == (Object)null)
			{
				return;
			}
			if (!((AbstractCloneStarObject)((ClassAbility)val).LocalPlayer).IsMine)
			{
				if (VoidManagerPlugin.Enabled)
				{
					GrappleClone.Install(val);
				}
				else
				{
					GrappleClone.Uninstall(GrappleClone.LocomotionOf((Ability)(object)val));
				}
				return;
			}
			LocalDonor = val;
			HookIsNative = GrappleClone.IsSelected((ClassAbility)(object)val);
			if (HookIsNative || !VoidManagerPlugin.Enabled)
			{
				GrappleClone.Uninstall(GrappleClone.LocomotionOf((Ability)(object)val));
			}
			else
			{
				GrappleClone.Install(val);
			}
		}

		internal static void Forget(ClassAbility ability)
		{
			try
			{
				GrappleClone.OnCharacterGone(ability);
				if (LocalDonor != null && (object)LocalDonor == ability)
				{
					LocalDonor = null;
					HookIsNative = false;
				}
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: teardown failed: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(ClassAbility), "Start")]
	internal static class ClassAbility_Start_Patch
	{
		private static bool Prefix(ClassAbility __instance)
		{
			try
			{
				return !GrappleClone.IsOurs(__instance);
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: Start prefix failed: " + ex));
				return true;
			}
		}

		private static void Postfix(ClassAbility __instance)
		{
			GrappleLifecycle.Serve(__instance);
		}
	}
	[HarmonyPatch(typeof(ClassAbility), "VerifyIfIsSelectedAbility")]
	internal static class ClassAbility_Verify_Patch
	{
		private static void Postfix(ClassAbility __instance)
		{
			GrappleLifecycle.Serve(__instance);
		}
	}
	[HarmonyPatch(typeof(ClassAbility), "OnDestroy")]
	internal static class ClassAbility_OnDestroy_Patch
	{
		private static void Postfix(ClassAbility __instance)
		{
			GrappleLifecycle.Forget(__instance);
		}
	}
	[HarmonyPatch(typeof(ClassAbility), "IsSuperiorVersion")]
	internal static class ClassAbility_Superior_Patch
	{
		private static bool Prefix(ClassAbility __instance, ref bool __result)
		{
			try
			{
				if (!GrappleClone.IsOurs(__instance))
				{
					return true;
				}
				__result = BepinPlugin.ForceAdvancedHook.Value && VoidManagerPlugin.Enabled && (Object)(object)__instance.LocalPlayer != (Object)null && ((AbstractCloneStarObject)__instance.LocalPlayer).IsMine;
				return false;
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: IsSuperiorVersion prefix failed: " + ex));
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(ClassAbility), "IsCapstonePerkSelected")]
	internal static class ClassAbility_Capstone_Patch
	{
		private static bool Prefix(ClassAbility __instance, ref bool __result)
		{
			try
			{
				if (!GrappleClone.IsOurs(__instance))
				{
					return true;
				}
				__result = false;
				return false;
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: IsCapstonePerkSelected prefix failed: " + ex));
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(FiniteDurationClassAbility), "CanStartAbility")]
	internal static class FiniteDurationClassAbility_CanStart_Patch
	{
		private static void Postfix(ClassAbility __instance, ref bool __result)
		{
			try
			{
				if (__result && __instance is PrivateGrapplingHook { Seated: not false })
				{
					__result = false;
				}
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: CanStartAbility postfix failed: " + ex));
			}
		}
	}
	[HarmonyPatch(typeof(ClassAbility), "GetHeader")]
	internal static class ClassAbility_GetHeader_Patch
	{
		private static bool Prefix(ClassAbility __instance, ref string __result)
		{
			try
			{
				if (!GrappleClone.IsOurs(__instance))
				{
					return true;
				}
				__result = "Grappling Hook";
				return false;
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: GetHeader prefix failed: " + ex));
				return true;
			}
		}
	}
	[HarmonyPatch(typeof(ClassAbility), "GetDescription")]
	internal static class ClassAbility_GetDescription_Patch
	{
		private static bool Prefix(ClassAbility __instance, ref string __result)
		{
			try
			{
				if (!GrappleClone.IsOurs(__instance))
				{
					return true;
				}
				__result = "Granted to every role by Personal Grapple.";
				return false;
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: GetDescription prefix failed: " + ex));
				return true;
			}
		}
	}
	public class GrappleSettingsMenu : ModSettingsMenu
	{
		public override string Name()
		{
			return "Personal Grapple";
		}

		public override void Draw()
		{
			GUILayout.Label("Grapple key: " + BepinPlugin.GrappleKey.Value, Array.Empty<GUILayoutOption>());
			if (GrappleKeyDriver.AwaitingRebind)
			{
				GUILayout.Label("Press the new key now... (Esc cancels)", Array.Empty<GUILayoutOption>());
			}
			else if (GUILayout.Button("Rebind grapple key", Array.Empty<GUILayoutOption>()))
			{
				GrappleKeyDriver.AwaitingRebind = true;
			}
			GUILayout.Space(8f);
			BepinPlugin.ShowGrappleHudIcon.Value = GUILayout.Toggle(BepinPlugin.ShowGrappleHudIcon.Value, "Show grapple icon on the HUD (beside your role ability)", Array.Empty<GUILayoutOption>());
			BepinPlugin.UseSharedAbilityKey.Value = GUILayout.Toggle(BepinPlugin.UseSharedAbilityKey.Value, "ALSO fire on the vanilla Role Ability key (triggers both abilities; applies on next spawn)", Array.Empty<GUILayoutOption>());
			GUILayout.Space(8f);
			GUILayout.Label("Settings are saved to the BepInEx config immediately.", Array.Empty<GUILayoutOption>());
			MenuKit.DevZone("grapple", DrawDevTools);
		}

		private static void DrawDevTools()
		{
			BepinPlugin.ForceAdvancedHook.Value = GUILayout.Toggle(BepinPlugin.ForceAdvancedHook.Value, "Advanced hook (can yank carryables, like the upgraded Scavenger perk)", Array.Empty<GUILayoutOption>());
		}
	}
	internal static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "jack.voidcrew.personalgrapple";

		public const string PLUGIN_NAME = "PersonalGrapple";

		public const string USERS_PLUGIN_NAME = "Personal Grapple";

		public const string PLUGIN_VERSION = "0.11.1";

		public const string PLUGIN_AUTHORS = "Jack";

		public const string PLUGIN_DESCRIPTION = "Gives the Scavenger grappling hook to every role, alongside their normal role ability.";

		public const string PLUGIN_THUNDERSTORE_ID = "Jack_Modding/Personal_Grapple";
	}
	[BepInPlugin("jack.voidcrew.personalgrapple", "Personal Grapple", "0.11.1")]
	[BepInProcess("Void Crew.exe")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency("jack.voidcrew.ectype", "0.15.0")]
	public class BepinPlugin : BaseUnityPlugin
	{
		internal static ManualLogSource Log;

		internal static ConfigEntry<string> GrappleKey;

		internal static ConfigEntry<bool> UseSharedAbilityKey;

		internal static ConfigEntry<bool> ForceAdvancedHook;

		internal static ConfigEntry<bool> ShowGrappleHudIcon;

		internal static ConfigEntry<string> GrappleIconColor;

		internal static ConfigEntry<bool> VerboseLogging;

		internal static bool Verbose
		{
			get
			{
				if (VerboseLogging != null)
				{
					return VerboseLogging.Value;
				}
				return false;
			}
		}

		private void Awake()
		{
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0121: Unknown result type (might be due to invalid IL or missing references)
			//IL_0127: Expected O, but got Unknown
			//IL_0127: Unknown result type (might be due to invalid IL or missing references)
			Log = ((BaseUnityPlugin)this).Logger;
			GrappleKey = ((BaseUnityPlugin)this).Config.Bind<string>("Input", "GrappleKey", "Digit2", "Key that fires the grappling hook for non-Scavenger roles. Accepts digits (2) or UnityEngine.InputSystem.Key names (h, g, f4, numpad5). Also rebindable in-game: VoidManager menu (F5) -> Mod Settings.");
			UseSharedAbilityKey = ((BaseUnityPlugin)this).Config.Bind<bool>("Input", "UseSharedClassAbilityKey", false, "If true, the grapple additionally stays bound to the vanilla Role Ability key (Settings -> Controls). Warning: that key will then trigger BOTH your native role ability AND the grapple at once.");
			ForceAdvancedHook = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "ForceAdvancedHook", false, "Developer setting. Treat the hook as the upgraded 'superior' version (it can yank carryables) even without the Scavenger perk.");
			ShowGrappleHudIcon = ((BaseUnityPlugin)this).Config.Bind<bool>("UI", "ShowGrappleHudIcon", true, "Show the grapple's button on the HUD ability bar, beside your role ability, with its key and a live cooldown/channel fill.");
			GrappleIconColor = ((BaseUnityPlugin)this).Config.Bind<string>("UI", "GrappleIconColor", "A855F7", "Colour of the grapple HUD button once it is ready (vivid purple by default). Empty = the game's orange.");
			VerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "VerboseLogging", false, "Log per-character events: hook installs and uninstalls on your character and crewmates' replicas, and every refused key press including ordinary cooldown ones.");
			Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null);
			List<string> list = PrivateGrapplingHook.MissingMembers();
			if (list.Count > 0)
			{
				Log.LogWarning((object)("Grapple: game members not found (game update?): " + string.Join(", ", list.ToArray()) + ". The hook may misbehave."));
			}
			GameObject val = new GameObject("PersonalGrapple.KeyDriver");
			Object.DontDestroyOnLoad((Object)val);
			((Object)val).hideFlags = (HideFlags)61;
			val.AddComponent<GrappleKeyDriver>();
			GrappleHudButton.Register();
			Log.LogInfo((object)"Personal Grapple v0.11.1 loaded.");
		}
	}
	public class VoidManagerPlugin : VoidPlugin
	{
		public static bool Enabled { get; private set; } = true;

		public override MultiplayerType MPType => (MultiplayerType)20;

		public override string Author => "Jack";

		public override string Description => "Gives the Scavenger grappling hook to every role, alongside their normal role ability.";

		public override string ThunderstoreID => "Jack_Modding/Personal_Grapple";

		public override SessionChangedReturn OnSessionChange(SessionChangedInput input)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: 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)
			Enabled = input.IsMod_Session;
			return new SessionChangedReturn
			{
				SetMod_Session = true
			};
		}
	}
	internal class PrivateGrapplingHook : ChannelGrapplingHook
	{
		private static readonly FieldInfo ProjectilePrefabF = AccessTools.Field(typeof(ChannelGrapplingHook), "headProjectilePrefab");

		private static readonly FieldInfo ProjSpeedF = AccessTools.Field(typeof(ChannelGrapplingHook), "projectileSpeed");

		private static readonly FieldInfo RangeF = AccessTools.Field(typeof(ChannelGrapplingHook), "range");

		private static readonly FieldInfo MaxSpeedF = AccessTools.Field(typeof(ChannelGrapplingHook), "characterGrappleMaxSpeed");

		private static readonly FieldInfo AccelF = AccessTools.Field(typeof(ChannelGrapplingHook), "characterGrappleAcceleration");

		private static readonly FieldInfo UpwardsPushF = AccessTools.Field(typeof(ChannelGrapplingHook), "characterUpwardsPushWhenGrappleStart");

		private static readonly FieldInfo ReachedDistanceF = AccessTools.Field(typeof(ChannelGrapplingHook), "consideredReachedDistance");

		private static readonly FieldInfo HoldTimeF = AccessTools.Field(typeof(ChannelGrapplingHook), "timeToConsiderHold");

		private static readonly FieldInfo MissCooldownF = AccessTools.Field(typeof(ChannelGrapplingHook), "cooldownWhenHookMiss");

		private static readonly FieldInfo PerkLoaderF = AccessTools.Field(typeof(ClassAbility), "LocalPerkLoader");

		private static readonly FieldInfo UsableF = AccessTools.Field(typeof(ClassAbility), "abilityUsable");

		private static readonly FieldInfo TagsF = AccessTools.Field(typeof(ClassAbility), "tags");

		private static readonly FieldInfo CooldownF = AccessTools.Field(typeof(ClassAbility), "CooldownSeconds");

		private static readonly FieldInfo DurationF = AccessTools.Field(typeof(FiniteDurationClassAbility), "FiniteTimeDuration");

		private static readonly FieldInfo AboutToEndF = AccessTools.Field(typeof(FiniteDurationClassAbility), "AboutToEndThreshold");

		private bool notReadyLogged;

		private SittingInChair seat;

		private bool seatResolved;

		private bool seatUnavailable;

		internal bool Seated
		{
			get
			{
				if (seatUnavailable)
				{
					return false;
				}
				if (!seatResolved)
				{
					try
					{
						seat = ((Ability)this).GetAbility<SittingInChair>();
						seatResolved = true;
						if (seat == null)
						{
							BepinPlugin.Log.LogWarning((object)"Grapple: character has no SittingInChair ability; hook not blocked while seated.");
						}
					}
					catch (Exception ex)
					{
						seatUnavailable = true;
						BepinPlugin.Log.LogError((object)("Grapple: resolving SittingInChair failed; hook not blocked while seated: " + ex));
						return false;
					}
				}
				if (seat != null)
				{
					return ((Ability)seat).IsActive;
				}
				return false;
			}
		}

		internal static PrivateGrapplingHook Create(ChannelGrapplingHook donor, UltimateCharacterLocomotion loco)
		{
			PrivateGrapplingHook privateGrapplingHook = new PrivateGrapplingHook();
			Take(ProjectilePrefabF, donor, privateGrapplingHook);
			Take(ProjSpeedF, donor, privateGrapplingHook);
			Take(MaxSpeedF, donor, privateGrapplingHook);
			Take(AccelF, donor, privateGrapplingHook);
			Take(UpwardsPushF, donor, privateGrapplingHook);
			Take(ReachedDistanceF, donor, privateGrapplingHook);
			Take(HoldTimeF, donor, privateGrapplingHook);
			Take(MissCooldownF, donor, privateGrapplingHook);
			Take(AboutToEndF, donor, privateGrapplingHook);
			CopyModifiable(RangeF, donor, privateGrapplingHook);
			CopyModifiable(CooldownF, donor, privateGrapplingHook);
			CopyModifiable(DurationF, donor, privateGrapplingHook);
			if (PerkLoaderF != null)
			{
				PerkLoaderF.SetValue(privateGrapplingHook, ((Component)loco).GetComponent<PlayerPerkLoader>());
			}
			if (UsableF != null)
			{
				UsableF.SetValue(privateGrapplingHook, true);
			}
			if (TagsF != null)
			{
				TagsF.SetValue(privateGrapplingHook, new CsTag[0]);
			}
			((Ability)privateGrapplingHook).StartType = (AbilityStartType)1;
			((Ability)privateGrapplingHook).StopType = (AbilityStopType)1;
			return privateGrapplingHook;
		}

		public override bool AbilityWillStart()
		{
			try
			{
				ILookSource val = (((Object)(object)((Ability)this).m_CharacterLocomotion != (Object)null) ? ((Ability)this).m_CharacterLocomotion.LookSource : null);
				Object val2 = (Object)(object)((val is Object) ? val : null);
				if ((val2 != null) ? (val2 != (Object)null) : (val != null))
				{
					return ((Ability)this).AbilityWillStart();
				}
				if (!notReadyLogged)
				{
					notReadyLogged = true;
					BepinPlugin.Log.LogWarning((object)"Grapple: start refused on a character with no look source yet (replica still spawning); logged once per character.");
				}
				return false;
			}
			catch (Exception ex)
			{
				BepinPlugin.Log.LogError((object)("Grapple: start readiness check failed: " + ex));
				return false;
			}
		}

		protected override void AbilityStarted()
		{
			GrappleClone.OnCloneStarting(this);
			((FiniteDurationClassAbility)this).AbilityStarted();
		}

		protected override void AbilityStopped(bool force)
		{
			((FiniteDurationClassAbility)this).AbilityStopped(force);
			GrappleClone.OnCloneStopped(this);
		}

		public override void Update()
		{
			if (Seated)
			{
				((Ability)this).StopAbility(true);
			}
			else
			{
				((FiniteDurationClassAbility)this).Update();
			}
		}

		private static void Take(FieldInfo f, ChannelGrapplingHook donor, PrivateGrapplingHook hook)
		{
			if (!(f == null))
			{
				f.SetValue(hook, f.GetValue(donor));
			}
		}

		private static void CopyModifiable(FieldInfo f, ChannelGrapplingHook donor, PrivateGrapplingHook hook)
		{
			if (!(f == null))
			{
				object? value = f.GetValue(donor);
				ModifiableFloat val = (ModifiableFloat)((value is ModifiableFloat) ? value : null);
				if (val != null)
				{
					f.SetValue(hook, ModifiableFloat.op_Implicit(((ModifiablePrimitive<float, FloatModifier>)(object)val).Value));
				}
			}
		}

		internal static List<string> MissingMembers()
		{
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, FieldInfo> item in new Dictionary<string, FieldInfo>
			{
				{ "headProjectilePrefab", ProjectilePrefabF },
				{ "projectileSpeed", ProjSpeedF },
				{ "range", RangeF },
				{ "characterGrappleMaxSpeed", MaxSpeedF },
				{ "characterGrappleAcceleration", AccelF },
				{ "characterUpwardsPushWhenGrappleStart", UpwardsPushF },
				{ "consideredReachedDistance", ReachedDistanceF },
				{ "timeToConsiderHold", HoldTimeF },
				{ "cooldownWhenHookMiss", MissCooldownF },
				{ "LocalPerkLoader", PerkLoaderF },
				{ "abilityUsable", UsableF },
				{ "tags", TagsF },
				{ "CooldownSeconds", CooldownF },
				{ "FiniteTimeDuration", DurationF },
				{ "AboutToEndThreshold", AboutToEndF }
			})
			{
				if (item.Value == null)
				{
					list.Add(item.Key);
				}
			}
			return list;
		}
	}
}