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