using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using UnityEngine;
using UnityEngine.UI;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("UsefulMutation")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("UsefulMutation")]
[assembly: AssemblyTitle("UsefulMutation")]
[assembly: AssemblyVersion("1.0.0.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;
}
}
}
namespace UsefulMutation
{
internal static class ModAssets
{
private static string _assetsDir;
private static string AssetsDir
{
get
{
if (_assetsDir == null)
{
_assetsDir = Path.Combine(Path.GetDirectoryName(typeof(ModAssets).Assembly.Location) ?? ".", "Assets");
}
return _assetsDir;
}
}
public static byte[] GetPngBytes(string fileName, bool allowEmbedded = true)
{
try
{
string text = Path.Combine(AssetsDir, fileName);
if (File.Exists(text))
{
ModConfig.Verbose("Using override texture " + text);
return File.ReadAllBytes(text);
}
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("Could not read override '" + fileName + "': " + ex.Message));
}
if (!allowEmbedded)
{
return null;
}
using Stream stream = typeof(ModAssets).Assembly.GetManifestResourceStream("UsefulMutation.Resources." + fileName);
if (stream == null)
{
return null;
}
using MemoryStream memoryStream = new MemoryStream();
stream.CopyTo(memoryStream);
return memoryStream.ToArray();
}
public static Texture2D LoadTexture(byte[] png, string name)
{
//IL_0009: Unknown result type (might be due to invalid IL or missing references)
//IL_000e: 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_001c: Unknown result type (might be due to invalid IL or missing references)
//IL_0024: Expected O, but got Unknown
//IL_0046: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
if (png == null)
{
return null;
}
Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false)
{
name = name,
filterMode = (FilterMode)0,
wrapMode = (TextureWrapMode)1
};
if (!ImageConversion.LoadImage(val, png, false))
{
Object.Destroy((Object)(object)val);
return null;
}
((Texture)val).filterMode = (FilterMode)0;
((Texture)val).wrapMode = (TextureWrapMode)1;
((Object)val).hideFlags = (HideFlags)(((Object)val).hideFlags | 0x20);
return val;
}
public static Sprite MakeSprite(Texture2D tex, Sprite likeThis, string name)
{
//IL_009f: Unknown result type (might be due to invalid IL or missing references)
//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
//IL_00b6: Unknown result type (might be due to invalid IL or missing references)
//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
//IL_0033: Unknown result type (might be due to invalid IL or missing references)
//IL_0038: Unknown result type (might be due to invalid IL or missing references)
//IL_003b: Unknown result type (might be due to invalid IL or missing references)
//IL_0040: 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_004e: Unknown result type (might be due to invalid IL or missing references)
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_0068: Unknown result type (might be due to invalid IL or missing references)
//IL_0070: 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)
if ((Object)(object)tex == (Object)null)
{
return null;
}
float num = 100f;
Vector2 val = default(Vector2);
((Vector2)(ref val))..ctor(0.5f, 0.5f);
if ((Object)(object)likeThis != (Object)null)
{
num = likeThis.pixelsPerUnit;
Rect rect = likeThis.rect;
Vector2 size = ((Rect)(ref rect)).size;
if (size.x > 0f && size.y > 0f)
{
((Vector2)(ref val))..ctor(likeThis.pivot.x / size.x, likeThis.pivot.y / size.y);
}
}
Sprite obj = Sprite.Create(tex, new Rect(0f, 0f, (float)((Texture)tex).width, (float)((Texture)tex).height), val, num, 0u, (SpriteMeshType)0);
((Object)obj).name = name;
((Object)obj).hideFlags = (HideFlags)(((Object)obj).hideFlags | 0x20);
return obj;
}
}
internal enum ThirdHandControl
{
Both,
WheelClickOnly,
WheelScrollOnly
}
internal static class ModConfig
{
public static ConfigEntry<ThirdHandControl> Control;
public static ConfigEntry<bool> ShareStaminaLoad;
public static ConfigEntry<float> SpawnWeight;
public static ConfigEntry<float> StaminaPenaltyPercent;
public static ConfigEntry<float> ClimbSpeedPenaltyPercent;
public static ConfigEntry<float> HandOffsetX;
public static ConfigEntry<float> HandOffsetY;
public static ConfigEntry<float> HandOffsetZ;
public static ConfigEntry<float> HandScale;
public static ConfigEntry<float> HudOffsetX;
public static ConfigEntry<float> HudOffsetY;
public static ConfigEntry<bool> VerboseLogging;
public static void Bind(ConfigFile cfg)
{
//IL_005f: Unknown result type (might be due to invalid IL or missing references)
//IL_0069: Expected O, but got Unknown
//IL_0097: Unknown result type (might be due to invalid IL or missing references)
//IL_00a1: Expected O, but got Unknown
//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
//IL_00d9: Expected O, but got Unknown
Control = cfg.Bind<ThirdHandControl>("General", "Control", ThirdHandControl.Both, "How the third hand is controlled. Both = middle mouse button (wheel click, hold) or wheel scroll (up = grab, down = release).");
ShareStaminaLoad = cfg.Bind<bool>("General", "ShareStaminaLoad", true, "Vanilla splits stamina drain across two hanging hands. With this on, the drain is split across every hanging hand, so three hands drain each hand at one third of the rate.");
SpawnWeight = cfg.Bind<float>("General", "SpawnWeight", 1f, new ConfigDescription("Relative chance for the perk to be offered by the experimental terminal (vanilla experimental perks use 1). Applied when the game starts.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0.01f, 2f), Array.Empty<object>()));
StaminaPenaltyPercent = cfg.Bind<float>("Balance", "MaxStaminaPenaltyPercent", 10f, new ConfigDescription("How much the perk lowers the maximum stamina of ALL hands (percent). Applied when the game starts.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>()));
ClimbSpeedPenaltyPercent = cfg.Bind<float>("Balance", "ClimbSpeedPenaltyPercent", 10f, new ConfigDescription("How much the perk lowers climbing speed (percent). Applied when the game starts.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(0f, 90f), Array.Empty<object>()));
HandOffsetX = cfg.Bind<float>("Visuals", "HandOffsetX", 0f, "Horizontal offset of the third hand (0 = screen centre, between the two vanilla hands).");
HandOffsetY = cfg.Bind<float>("Visuals", "HandOffsetY", -0.12f, "Vertical offset of the third hand relative to the vanilla hands (negative = lower).");
HandOffsetZ = cfg.Bind<float>("Visuals", "HandOffsetZ", 0f, "Depth offset of the third hand relative to the vanilla hands.");
HandScale = cfg.Bind<float>("Visuals", "HandScaleMultiplier", 1f, "Size multiplier of the third hand sprite.");
HudOffsetX = cfg.Bind<float>("Visuals", "HudIconOffsetX", 0f, "Extra horizontal offset (pixels) of the third hand's crosshair/stamina icon.");
HudOffsetY = cfg.Bind<float>("Visuals", "HudIconOffsetY", 0f, "Extra vertical offset (pixels) of the third hand's crosshair/stamina icon.");
VerboseLogging = cfg.Bind<bool>("Debug", "VerboseLogging", false, "Log extra information about the third hand setup.");
}
public static void Verbose(string msg)
{
if (VerboseLogging != null && VerboseLogging.Value)
{
Plugin.Log.LogInfo((object)msg);
}
}
}
internal sealed class ModHost : MonoBehaviour
{
private float _nextRegisterTry;
private void Update()
{
if (!PerkFactory.IsRegistered && Time.unscaledTime >= _nextRegisterTry)
{
_nextRegisterTry = Time.unscaledTime + 0.5f;
PerkFactory.EnsureRegistered();
}
ENT_Player playerObject = ENT_Player.playerObject;
if (!((Object)(object)playerObject == (Object)null))
{
bool shouldHave = false;
try
{
shouldHave = playerObject.HasPerk("usefulmutation_third_hand");
}
catch
{
}
ThirdHand.Sync(playerObject, shouldHave);
if (ThirdHand.IsAttached)
{
CL_GameManager gMan = CL_GameManager.gMan;
ThirdHandInput.Tick((Object)(object)gMan != (Object)null && !gMan.isPaused && !gMan.lockPlayerInput && !CommandConsole.IsConsoleVisible() && !playerObject.IsLocked() && !((GameEntity)playerObject).dead, ThirdHand.Hand != null && ThirdHand.Hand.IsHolding());
}
}
}
}
internal static class Patches
{
[HarmonyPatch(typeof(InputManager), "GetButton")]
internal static class InputManagerGetButtonPatch
{
[HarmonyPrefix]
private static bool Prefix(string name, ref ButtonInput __result)
{
if (ThirdHandInput.IsThirdHandButton(name))
{
__result = ThirdHandInput.Get(name);
return false;
}
return true;
}
}
[HarmonyPatch(typeof(ENT_Player), "Actions")]
internal static class ActionsPatch
{
[HarmonyPostfix]
private static void Postfix(ENT_Player __instance)
{
ThirdHand.RunInteraction(__instance);
}
}
[HarmonyPatch(typeof(ENT_Player), "HandStamina")]
internal static class HandStaminaPatch
{
[HarmonyPrefix]
private static void Prefix(ENT_Player __instance)
{
if (ModConfig.ShareStaminaLoad.Value && ThirdHand.HasThreeHands(__instance))
{
int num = ThirdHand.CountHanging(__instance);
GripLossRate.Invoke(__instance) = ((num >= 2) ? (1f / (float)num) : 1f);
}
}
}
[HarmonyPatch(typeof(ENT_Player), "Interact")]
internal static class InteractPatch
{
[HarmonyPrefix]
private static bool Prefix(Interaction interaction, ref bool interacting, ref Sprite interactSprite)
{
if (interaction?.curhand != null && ThirdHand.IsThirdHand(interaction.curhand) && (Object)(object)interaction.tagger != (Object)null && interaction.tagger.HasTag("Item"))
{
interactSprite = null;
interacting = false;
return false;
}
return true;
}
}
[HarmonyPatch(typeof(OS_Manager), "Update")]
internal static class OsManagerUpdatePatch
{
private static Hand[] _saved;
private static ENT_Player _savedFor;
private static Hand[] _pair = (Hand[])(object)new Hand[2];
[HarmonyPrefix]
private static void Prefix()
{
ENT_Player playerObject = ENT_Player.playerObject;
if (!((Object)(object)playerObject == (Object)null) && playerObject.hands != null && playerObject.hands.Length > 2)
{
_saved = playerObject.hands;
_savedFor = playerObject;
_pair[0] = _saved[0];
_pair[1] = _saved[1];
playerObject.hands = _pair;
}
}
[HarmonyFinalizer]
private static void Finalizer()
{
if (_saved != null)
{
if ((Object)(object)_savedFor != (Object)null)
{
_savedFor.hands = _saved;
}
_saved = null;
_savedFor = null;
}
}
}
[HarmonyPatch(typeof(PerkModule_HoldDrop_CreateItem), "Update")]
internal static class HoldDropCreateItemPatch
{
private static readonly FieldRef<PerkModule_HoldDrop_CreateItem, float[]> HoldTime = AccessTools.FieldRefAccess<PerkModule_HoldDrop_CreateItem, float[]>("holdTime");
private static readonly FieldRef<PerkModule_HoldDrop_CreateItem, float[]> RecoveryTime = AccessTools.FieldRefAccess<PerkModule_HoldDrop_CreateItem, float[]>("recoveryTime");
private static readonly FieldRef<PerkModule_HoldDrop_CreateItem, bool[]> Holding = AccessTools.FieldRefAccess<PerkModule_HoldDrop_CreateItem, bool[]>("holding");
[HarmonyPrefix]
private static void Prefix(PerkModule_HoldDrop_CreateItem __instance)
{
Fix(__instance);
}
internal static void Fix(PerkModule_HoldDrop_CreateItem m)
{
if (HoldTime.Invoke(m).Length < 3)
{
Array.Resize(ref HoldTime.Invoke(m), 3);
}
if (RecoveryTime.Invoke(m).Length < 3)
{
Array.Resize(ref RecoveryTime.Invoke(m), 3);
}
if (Holding.Invoke(m).Length < 3)
{
Array.Resize(ref Holding.Invoke(m), 3);
}
}
}
[HarmonyPatch(typeof(PerkModule_JazzHand), "Update")]
internal static class JazzHandPatch
{
private static readonly FieldRef<PerkModule_JazzHand, float[]> LastOpen = AccessTools.FieldRefAccess<PerkModule_JazzHand, float[]>("lastOpenTime");
[HarmonyPrefix]
private static void Prefix(PerkModule_JazzHand __instance)
{
Fix(__instance);
}
internal static void Fix(PerkModule_JazzHand m)
{
ref float[] reference = ref LastOpen.Invoke(m);
if (reference == null)
{
reference = new float[3];
}
else if (reference.Length < 3)
{
Array.Resize(ref reference, 3);
}
}
}
[HarmonyPatch(typeof(App_PerkPage), "GenerateCards")]
internal static class PerkPageGenerateCardsPatch
{
[HarmonyPrefix]
private static void Prefix()
{
PerkFactory.EnsureRegistered();
}
}
[HarmonyPatch(typeof(CL_AssetManager), "GetPerkAsset")]
internal static class GetPerkAssetPatch
{
[HarmonyPrefix]
private static void Prefix()
{
PerkFactory.EnsureRegistered();
}
}
private static readonly FieldRef<ENT_Player, float> GripLossRate = AccessTools.FieldRefAccess<ENT_Player, float>("gripStrengthLossRate");
}
internal static class PerkFactory
{
private static bool _registered;
private static bool _failedOnce;
public static Perk Perk { get; private set; }
public static bool IsRegistered => _registered;
public static void EnsureRegistered()
{
//IL_0057: Unknown result type (might be due to invalid IL or missing references)
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
if (_registered)
{
return;
}
WKAssetDatabase baseDatabase = CL_AssetManager.baseDatabase;
if ((Object)(object)baseDatabase == (Object)null || baseDatabase.perkAssets == null || baseDatabase.perkAssets.Count == 0)
{
return;
}
try
{
Perk = Build(baseDatabase);
WKAssetDatabase val = ScriptableObject.CreateInstance<WKAssetDatabase>();
val.id = "usefulmutation";
((Object)val).name = "UsefulMutation_Database";
((Object)val).hideFlags = (HideFlags)(((Object)val).hideFlags | 0x20);
val.perkAssets.Add(Perk);
CL_AssetManager.AddNewDatabase("usefulmutation", val);
_registered = true;
Plugin.Log.LogInfo((object)("Registered perk '" + Perk.title + "' (" + Perk.id + ") in the experimental perk pool."));
}
catch (Exception arg)
{
if (!_failedOnce)
{
_failedOnce = true;
Plugin.Log.LogError((object)$"Failed to register the Useful Mutation perk: {arg}");
}
}
}
private static Perk Build(WKAssetDatabase baseDb)
{
//IL_0053: Unknown result type (might be due to invalid IL or missing references)
//IL_0059: Invalid comparison between Unknown and I4
//IL_0180: Unknown result type (might be due to invalid IL or missing references)
//IL_0187: Unknown result type (might be due to invalid IL or missing references)
//IL_0216: Unknown result type (might be due to invalid IL or missing references)
//IL_021d: Unknown result type (might be due to invalid IL or missing references)
//IL_02ab: Unknown result type (might be due to invalid IL or missing references)
//IL_02b0: Unknown result type (might be due to invalid IL or missing references)
//IL_02bb: Unknown result type (might be due to invalid IL or missing references)
//IL_02c6: Unknown result type (might be due to invalid IL or missing references)
//IL_02cd: Unknown result type (might be due to invalid IL or missing references)
//IL_02d8: Unknown result type (might be due to invalid IL or missing references)
//IL_02df: Unknown result type (might be due to invalid IL or missing references)
//IL_02ea: Unknown result type (might be due to invalid IL or missing references)
//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
//IL_02fc: Unknown result type (might be due to invalid IL or missing references)
//IL_0301: Unknown result type (might be due to invalid IL or missing references)
//IL_030c: Unknown result type (might be due to invalid IL or missing references)
//IL_031e: Expected O, but got Unknown
//IL_031f: Unknown result type (might be due to invalid IL or missing references)
//IL_0324: Unknown result type (might be due to invalid IL or missing references)
//IL_032f: Unknown result type (might be due to invalid IL or missing references)
//IL_0343: Expected O, but got Unknown
//IL_034d: Expected O, but got Unknown
//IL_0355: Unknown result type (might be due to invalid IL or missing references)
//IL_035a: Unknown result type (might be due to invalid IL or missing references)
//IL_0365: Unknown result type (might be due to invalid IL or missing references)
//IL_0375: Expected O, but got Unknown
//IL_0381: Unknown result type (might be due to invalid IL or missing references)
//IL_0386: Unknown result type (might be due to invalid IL or missing references)
//IL_03a5: Unknown result type (might be due to invalid IL or missing references)
//IL_03b5: Expected O, but got Unknown
Perk val = null;
HashSet<string> hashSet = new HashSet<string> { "standard", "basic", "unstable" };
foreach (Perk perkAsset in baseDb.perkAssets)
{
if ((Object)(object)perkAsset == (Object)null || (int)perkAsset.spawnPool != 1)
{
continue;
}
if ((Object)(object)val == (Object)null || ((Object)(object)val.perkFrame == (Object)null && (Object)(object)perkAsset.perkFrame != (Object)null))
{
val = perkAsset;
}
if (perkAsset.tags == null)
{
continue;
}
foreach (string tag in perkAsset.tags)
{
hashSet.Add(tag);
}
}
if ((Object)(object)val == (Object)null)
{
Plugin.Log.LogWarning((object)"No vanilla experimental perk found to use as a template; falling back to the first perk.");
val = baseDb.perkAssets[0];
}
string[] obj = new string[7]
{
"Template perk: ",
((Object)val).name,
" (frame=",
null,
null,
null,
null
};
Sprite perkFrame = val.perkFrame;
obj[3] = ((perkFrame != null) ? ((Object)perkFrame).name : null);
obj[4] = ", iconMat=";
Material iconMat = val.iconMat;
obj[5] = ((iconMat != null) ? ((Object)iconMat).name : null);
obj[6] = ")";
ModConfig.Verbose(string.Concat(obj));
Perk val2 = Object.Instantiate<Perk>(val);
((Object)val2).name = "Perk_Unstable_UsefulMutation";
((Object)val2).hideFlags = (HideFlags)(((Object)val2).hideFlags | 0x20);
val2.id = "usefulmutation_third_hand";
val2.title = "Useful Mutation";
float num = Mathf.Clamp(ModConfig.StaminaPenaltyPercent.Value, 0f, 90f);
float num2 = Mathf.Clamp(ModConfig.ClimbSpeedPenaltyPercent.Value, 0f, 90f);
val2.description = "(+) Grafts a third hand between your two hands. Grip with the middle mouse button or the scroll wheel.\n(+) Hanging load is shared across every hand you hang from.\n" + $"(-) -{num:0.#}% max stamina on ALL hands\n" + $"(-) -{num2:0.#}% climbing speed";
val2.flavorText = "It itches. It also grips.";
val2.perkType = (PerkType)3;
val2.spawnPool = (PerkPool)1;
val2.tags = new List<string>(hashSet);
val2.canStack = false;
val2.stackMax = 1;
val2.stackAmount = 0;
val2.multiplierCurve = AnimationCurve.Linear(0f, 1f, 1f, 1f);
val2.competitive = false;
val2.unlockProgressionID = "";
val2.unlockXP = 0;
val2.spawnInEndless = true;
val2.flags = new List<string>();
val2.playerTag = new List<string>();
val2.modules = new List<PerkModule>();
val2.useBuff = true;
val2.buff = new BuffContainer
{
id = "usefulmutation_third_hand_buff",
desc = "",
loseOverTime = false,
loseRate = 0f,
loseRateEffectedByPerks = false,
buffTime = 1f,
multiplier = 1f,
buffs = new List<Buff>
{
new Buff
{
id = "limitGripStrength",
maxAmount = num / 100f
},
new Buff
{
id = "addClimb",
maxAmount = (0f - num2) / 100f
}
}
};
val2.useBaseBuff = false;
val2.baseBuff = new BuffContainer
{
id = "",
buffs = new List<Buff>()
};
val2.buffMultiplier = 1f;
val2.spawnSettings = new SpawnSettings
{
spawnChance = Mathf.Clamp(ModConfig.SpawnWeight.Value, 0.01f, 2f),
chanceMultiplier = 1f
};
Texture2D tex = ModAssets.LoadTexture(ModAssets.GetPngBytes("perk_icon.png"), "UsefulMutation_Icon");
Texture2D tex2 = ModAssets.LoadTexture(ModAssets.GetPngBytes("perk_card.png"), "UsefulMutation_Card");
Sprite val3 = ModAssets.MakeSprite(tex, val.icon, "Perk_Icon_Unstable_UsefulMutation");
Sprite val4 = ModAssets.MakeSprite(tex2, val.perkCard, "Perk_Card_Unstable_UsefulMutation");
if ((Object)(object)val3 != (Object)null)
{
val2.icon = val3;
}
if ((Object)(object)val4 != (Object)null)
{
val2.perkCard = val4;
}
return val2;
}
}
[BepInPlugin("com.usefulmutation.whiteknuckle", "Useful Mutation", "1.0.0")]
public sealed class Plugin : BaseUnityPlugin
{
internal static ManualLogSource Log;
internal static Plugin Instance;
private Harmony _harmony;
private void Awake()
{
//IL_0022: Unknown result type (might be due to invalid IL or missing references)
//IL_002c: Expected O, but got Unknown
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
//IL_0050: Unknown result type (might be due to invalid IL or missing references)
//IL_0056: Expected O, but got Unknown
//IL_0056: Unknown result type (might be due to invalid IL or missing references)
Instance = this;
Log = ((BaseUnityPlugin)this).Logger;
ModConfig.Bind(((BaseUnityPlugin)this).Config);
_harmony = new Harmony("com.usefulmutation.whiteknuckle");
_harmony.PatchAll(typeof(Plugin).Assembly);
GameObject val = new GameObject("UsefulMutation.Host");
Object.DontDestroyOnLoad((Object)val);
((Object)val).hideFlags = (HideFlags)61;
val.AddComponent<ModHost>();
Log.LogInfo((object)"Useful Mutation 1.0.0 loaded.");
}
private void OnDestroy()
{
Harmony harmony = _harmony;
if (harmony != null)
{
harmony.UnpatchSelf();
}
}
}
internal static class PluginInfo
{
public const string Guid = "com.usefulmutation.whiteknuckle";
public const string Name = "Useful Mutation";
public const string Version = "1.0.0";
public const string PerkId = "usefulmutation_third_hand";
public const string DatabaseId = "usefulmutation";
}
internal static class ThirdHand
{
public const int HandId = 2;
private static ENT_Player _owner;
private static Hand _hand;
private static Image _hud;
private static Image _hudOverlay;
private static bool _attached;
private static bool _visible;
private static float _nextAttempt;
private static readonly FieldInfo InitialPositionField = AccessTools.Field(typeof(ViewSway), "initialPosition");
private static readonly MethodInfo StartInteractionMethod = AccessTools.Method(typeof(ENT_Player), "StartInteraction", (Type[])null, (Type[])null);
private static readonly FieldRef<ENT_Player, Vector3[]> GrabAnywherePosition = AccessTools.FieldRefAccess<ENT_Player, Vector3[]>("grabAnywherePosition");
private static readonly FieldRef<ENT_Player, Transform[]> GrabAnywhereHit = AccessTools.FieldRefAccess<ENT_Player, Transform[]>("grabAnywhereHit");
public static bool IsAttached => _attached;
public static Hand Hand => _hand;
public static bool IsThirdHand(Hand h)
{
if (h != null)
{
return h.id == 2;
}
return false;
}
public static bool HasThreeHands(ENT_Player player)
{
if ((Object)(object)player != (Object)null && player.hands != null)
{
return player.hands.Length > 2;
}
return false;
}
public static void Sync(ENT_Player player, bool shouldHave)
{
if ((Object)(object)player == (Object)null)
{
return;
}
if ((Object)(object)_owner != (Object)(object)player)
{
ForgetOwner();
_owner = player;
}
if (shouldHave)
{
if (!_attached && Time.unscaledTime >= _nextAttempt)
{
Attach(player);
if (!_attached)
{
_nextAttempt = Time.unscaledTime + 5f;
}
}
if (_attached)
{
UpdateVisibility(player);
}
}
else if (_attached)
{
Detach(player);
}
}
private static void ForgetOwner()
{
_owner = null;
_hand = null;
_hud = null;
_hudOverlay = null;
_attached = false;
_visible = false;
ThirdHandInput.Reset();
}
private static void Attach(ENT_Player player)
{
try
{
if (_hand == null || (Object)(object)_hand.handModel == (Object)null)
{
_hand = Build(player);
if (_hand == null)
{
return;
}
}
if (player.hands.Length < 3)
{
Hand[] array = (Hand[])(object)new Hand[3];
Array.Copy(player.hands, array, Math.Min(player.hands.Length, 2));
array[2] = _hand;
player.hands = array;
}
_hand.gripStrength = player.GetCurrentGripStrengthTimer(2);
_hand.grabWait = 0.25f;
FixVanillaPerkArrays(player);
ThirdHandInput.Reset();
_attached = true;
_visible = false;
SetVisible(v: true);
Plugin.Log.LogInfo((object)"Third hand attached.");
}
catch (Exception arg)
{
Plugin.Log.LogError((object)$"Failed to attach the third hand: {arg}");
if (player.hands != null && player.hands.Length > 2)
{
Hand[] array2 = (Hand[])(object)new Hand[2];
Array.Copy(player.hands, array2, 2);
player.hands = array2;
}
_attached = false;
}
}
private static void Detach(ENT_Player player)
{
try
{
if (player.hands != null && player.hands.Length > 2)
{
try
{
player.StopInteraction(2, "", false);
}
catch (Exception ex)
{
Plugin.Log.LogWarning((object)("StopInteraction on detach failed: " + ex.Message));
}
Hand[] array = (Hand[])(object)new Hand[2];
Array.Copy(player.hands, array, 2);
player.hands = array;
}
}
finally
{
_attached = false;
SetVisible(v: false);
ThirdHandInput.Reset();
Plugin.Log.LogInfo((object)"Third hand detached.");
}
}
private static void UpdateVisibility(ENT_Player player)
{
bool flag = (Object)(object)OS_Manager.activeComputer == (Object)null || !OS_Manager.activeComputer.canControl;
if (flag != _visible)
{
SetVisible(flag);
}
}
private static void SetVisible(bool v)
{
_visible = v;
if ((Object)(object)_hand?.handModel != (Object)null)
{
((Component)_hand.handModel).gameObject.SetActive(v);
}
if (!v && !_attached && (Object)(object)_hud != (Object)null)
{
((Component)_hud).gameObject.SetActive(false);
}
}
public static void RunInteraction(ENT_Player player)
{
if (_attached && HasThreeHands(player) && _hand != null && (!((Object)(object)CL_GameManager.gMan != (Object)null) || !CL_GameManager.gMan.isPaused))
{
bool flag = Inventory.IsShowingInventory();
_hand.SetCanInteract(!flag || _hand.IsHolding());
StartInteractionMethod.Invoke(player, new object[1] { 2 });
}
}
public static int CountHanging(ENT_Player player)
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
//IL_001b: Invalid comparison between Unknown and I4
int num = 0;
Hand[] hands = player.hands;
foreach (Hand val in hands)
{
if (val != null && (int)val.interactState == 4)
{
num++;
}
}
return num;
}
private static Hand Build(ENT_Player player)
{
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_005e: Unknown result type (might be due to invalid IL or missing references)
//IL_0065: Unknown result type (might be due to invalid IL or missing references)
//IL_006c: Unknown result type (might be due to invalid IL or missing references)
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
//IL_0092: Unknown result type (might be due to invalid IL or missing references)
//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
//IL_00c6: Expected O, but got Unknown
//IL_00c6: Unknown result type (might be due to invalid IL or missing references)
//IL_00d1: Unknown result type (might be due to invalid IL or missing references)
//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
//IL_00eb: Expected O, but got Unknown
//IL_0195: Unknown result type (might be due to invalid IL or missing references)
//IL_019f: Expected O, but got Unknown
Hand val = player.hands[0];
if ((Object)(object)val?.handModel == (Object)null)
{
Plugin.Log.LogError((object)"Left hand model not found; cannot build the third hand.");
return null;
}
Transform handModel = val.handModel;
GameObject val2 = Object.Instantiate<GameObject>(((Component)handModel).gameObject, handModel.parent);
((Object)val2).name = "Hand_Third (Useful Mutation)";
Transform transform = val2.transform;
Hand val3 = new Hand
{
id = 2,
handModel = transform,
handSprite = MapComponent<SpriteRenderer>(handModel, transform, val.handSprite),
handForegroundSprite = MapComponent<SpriteRenderer>(handModel, transform, val.handForegroundSprite),
accessoryBase = MapComponent<Hand_Base>(handModel, transform, val.accessoryBase),
throwButton = "Hand-Third-Throw",
fireButton = "Hand-Third",
inventoryHand = new ItemHand(),
currentCosmetics = new List<Cosmetic_HandItem>(),
handPoses = BuildPoses(val),
gripStrength = player.gripStrengthTimer
};
if ((Object)(object)val3.handSprite == (Object)null || (Object)(object)val3.handForegroundSprite == (Object)null)
{
Plugin.Log.LogError((object)"Could not map hand sprite renderers onto the cloned model.");
Object.Destroy((Object)(object)val2);
return null;
}
HandItem[] componentsInChildren = val2.GetComponentsInChildren<HandItem>(true);
foreach (HandItem val4 in componentsInChildren)
{
if ((Object)(object)val4 != (Object)null && (Object)(object)((Component)val4).gameObject != (Object)(object)val2)
{
Object.Destroy((Object)(object)((Component)val4).gameObject);
}
}
SpriteRenderer[] componentsInChildren2 = val2.GetComponentsInChildren<SpriteRenderer>(true);
foreach (SpriteRenderer val5 in componentsInChildren2)
{
if ((Object)(object)((Renderer)val5).sharedMaterial != (Object)null)
{
((Renderer)val5).material = new Material(((Renderer)val5).sharedMaterial);
}
}
ConfigureSway(val, val3);
BuildHud(val, val3);
ExtendIdIndexedArrays(player);
val3.Initialize(player);
val3.currentCosmetics = new List<Cosmetic_HandItem>();
val3.HideInteractionSprite();
val3.SetSprite(val3.GetPoseSprite("normal"), (Sprite)null);
string[] obj = new string[6]
{
"Third hand built. model=",
((Object)transform).name,
" parent=",
null,
null,
null
};
Transform parent = transform.parent;
obj[3] = ((parent != null) ? ((Object)parent).name : null);
obj[4] = " sprite=";
obj[5] = ((Object)val3.handSprite).name;
ModConfig.Verbose(string.Concat(obj));
return val3;
}
private static T MapComponent<T>(Transform srcRoot, Transform dstRoot, T src) where T : Component
{
if ((Object)(object)src == (Object)null)
{
return default(T);
}
Transform val = MapTransform(srcRoot, dstRoot, ((Component)src).transform);
if (!((Object)(object)val != (Object)null))
{
return default(T);
}
return ((Component)val).GetComponent<T>();
}
private static Transform MapTransform(Transform srcRoot, Transform dstRoot, Transform t)
{
if ((Object)(object)t == (Object)null)
{
return null;
}
List<int> list = new List<int>();
Transform val = t;
while ((Object)(object)val != (Object)(object)srcRoot)
{
if ((Object)(object)val == (Object)null)
{
return null;
}
list.Add(val.GetSiblingIndex());
val = val.parent;
}
list.Reverse();
Transform val2 = dstRoot;
foreach (int item in list)
{
if (item >= val2.childCount)
{
return null;
}
val2 = val2.GetChild(item);
}
return val2;
}
private static List<HandPoses> BuildPoses(Hand source)
{
//IL_0089: Unknown result type (might be due to invalid IL or missing references)
//IL_008e: Unknown result type (might be due to invalid IL or missing references)
//IL_009a: Unknown result type (might be due to invalid IL or missing references)
//IL_00a6: Expected O, but got Unknown
List<HandPoses> list = new List<HandPoses>();
foreach (HandPoses handPose in source.handPoses)
{
Sprite sprite = handPose.sprite;
byte[] pngBytes = ModAssets.GetPngBytes("hand_" + handPose.id + ".png", allowEmbedded: false);
if (pngBytes != null)
{
Sprite val = ModAssets.MakeSprite(ModAssets.LoadTexture(pngBytes, "UsefulMutation_Hand_" + handPose.id), handPose.sprite, "UsefulMutation_Hand_" + handPose.id);
if ((Object)(object)val != (Object)null)
{
sprite = val;
}
}
list.Add(new HandPoses
{
id = handPose.id,
sprite = sprite
});
}
return list;
}
private static void ConfigureSway(Hand source, Hand hand)
{
//IL_0067: Unknown result type (might be due to invalid IL or missing references)
//IL_0071: Unknown result type (might be due to invalid IL or missing references)
//IL_0076: Unknown result type (might be due to invalid IL or missing references)
//IL_007c: Unknown result type (might be due to invalid IL or missing references)
//IL_0081: Unknown result type (might be due to invalid IL or missing references)
//IL_008d: Unknown result type (might be due to invalid IL or missing references)
//IL_009e: Unknown result type (might be due to invalid IL or missing references)
//IL_00af: Unknown result type (might be due to invalid IL or missing references)
//IL_00b4: Unknown result type (might be due to invalid IL or missing references)
//IL_004d: Unknown result type (might be due to invalid IL or missing references)
//IL_0052: Unknown result type (might be due to invalid IL or missing references)
//IL_0059: Unknown result type (might be due to invalid IL or missing references)
//IL_0106: Unknown result type (might be due to invalid IL or missing references)
ViewSway viewSway = source.GetViewSway();
ViewSway component = ((Component)hand.handModel).GetComponent<ViewSway>();
if ((Object)(object)viewSway == (Object)null || (Object)(object)component == (Object)null)
{
Plugin.Log.LogWarning((object)"ViewSway missing; the third hand will use its cloned position.");
return;
}
if (InitialPositionField != null)
{
Vector3 localPosition = (Vector3)InitialPositionField.GetValue(viewSway);
hand.handModel.localPosition = localPosition;
}
component.changeInitialScale = false;
component.targetOffset = Vector3.down * 0.4f;
Vector3 offset = viewSway.offset;
component.offset = new Vector3(ModConfig.HandOffsetX.Value, offset.y + ModConfig.HandOffsetY.Value, offset.z + ModConfig.HandOffsetZ.Value);
component.scale = viewSway.scale * ModConfig.HandScale.Value;
component.animationOffset = viewSway.animationOffset + 0.5f;
if (component.parameters != null)
{
component.parameters.bobBaseRotation = 0f;
}
hand.handModel.localScale = source.handModel.localScale;
}
private static void BuildHud(Hand source, Hand hand)
{
//IL_007f: Unknown result type (might be due to invalid IL or missing references)
//IL_0084: Unknown result type (might be due to invalid IL or missing references)
//IL_0103: Unknown result type (might be due to invalid IL or missing references)
//IL_0119: Unknown result type (might be due to invalid IL or missing references)
//IL_011e: Unknown result type (might be due to invalid IL or missing references)
//IL_0097: Unknown result type (might be due to invalid IL or missing references)
//IL_009c: Unknown result type (might be due to invalid IL or missing references)
//IL_009e: Unknown result type (might be due to invalid IL or missing references)
//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
//IL_00db: Unknown result type (might be due to invalid IL or missing references)
//IL_01f1: Unknown result type (might be due to invalid IL or missing references)
//IL_01f7: Unknown result type (might be due to invalid IL or missing references)
//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
//IL_0201: Unknown result type (might be due to invalid IL or missing references)
//IL_0211: Unknown result type (might be due to invalid IL or missing references)
//IL_0216: Unknown result type (might be due to invalid IL or missing references)
//IL_0218: Unknown result type (might be due to invalid IL or missing references)
if ((Object)(object)source.uiInteract == (Object)null)
{
Plugin.Log.LogWarning((object)"Left hand HUD icon not found; the third hand will have no crosshair icon.");
return;
}
Image uiInteract = source.uiInteract;
Image val = Object.Instantiate<Image>(uiInteract, ((Component)uiInteract).transform.parent);
((Object)val).name = "UI_Interact_Third (Useful Mutation)";
RectTransform rectTransform = ((Graphic)val).rectTransform;
RectTransform rectTransform2 = ((Graphic)uiInteract).rectTransform;
ENT_Player player = ENT_Player.GetPlayer();
Image val2 = ((player != null && player.hands.Length > 1) ? ENT_Player.GetPlayer().hands[1].uiInteract : null);
Vector2 anchoredPosition = rectTransform2.anchoredPosition;
if ((Object)(object)val2 != (Object)null)
{
Vector2 anchoredPosition2 = ((Graphic)val2).rectTransform.anchoredPosition;
float num = Mathf.Abs(anchoredPosition2.x - rectTransform2.anchoredPosition.x);
((Vector2)(ref anchoredPosition))..ctor((anchoredPosition2.x + rectTransform2.anchoredPosition.x) * 0.5f, Mathf.Min(anchoredPosition2.y, rectTransform2.anchoredPosition.y) - Mathf.Max(24f, num * 0.45f));
}
rectTransform.anchoredPosition = anchoredPosition + new Vector2(ModConfig.HudOffsetX.Value, ModConfig.HudOffsetY.Value);
(((Component)val).GetComponent<LayoutElement>() ?? ((Component)val).gameObject.AddComponent<LayoutElement>()).ignoreLayout = true;
hand.uiInteract = val;
_hud = val;
CL_UIManager instance = CL_UIManager.instance;
Image[] interactOverlays = instance.interactOverlays;
if (interactOverlays != null && interactOverlays.Length > 2)
{
return;
}
Image[] array = (Image[])(object)new Image[3];
if (interactOverlays != null)
{
Array.Copy(interactOverlays, array, Math.Min(interactOverlays.Length, 2));
}
Image val3 = null;
Image val4 = ((interactOverlays != null && interactOverlays.Length != 0) ? interactOverlays[0] : null);
if ((Object)(object)val4 != (Object)null)
{
Image val5 = MapComponent<Image>(((Component)uiInteract).transform, ((Component)val).transform, val4);
if ((Object)(object)val5 != (Object)null)
{
val3 = val5;
}
else
{
val3 = Object.Instantiate<Image>(val4, ((Component)val4).transform.parent);
((Object)val3).name = "UI_InteractOverlay_Third (Useful Mutation)";
Vector2 val6 = rectTransform.anchoredPosition - rectTransform2.anchoredPosition;
((Graphic)val3).rectTransform.anchoredPosition = ((Graphic)val4).rectTransform.anchoredPosition + val6;
}
}
array[2] = val3;
instance.interactOverlays = array;
_hudOverlay = val3;
}
private static void FixVanillaPerkArrays(ENT_Player player)
{
foreach (Perk perk in player.perks)
{
if (perk?.modules == null)
{
continue;
}
foreach (PerkModule module in perk.modules)
{
PerkModule_HoldDrop_CreateItem val = (PerkModule_HoldDrop_CreateItem)(object)((module is PerkModule_HoldDrop_CreateItem) ? module : null);
if (val != null)
{
Patches.HoldDropCreateItemPatch.Fix(val);
continue;
}
PerkModule_JazzHand val2 = (PerkModule_JazzHand)(object)((module is PerkModule_JazzHand) ? module : null);
if (val2 != null)
{
Patches.JazzHandPatch.Fix(val2);
}
}
}
}
private static void ExtendIdIndexedArrays(ENT_Player player)
{
CL_Handhold[] grabAnywhereFakeHandholds = player.grabAnywhereFakeHandholds;
if (grabAnywhereFakeHandholds != null && grabAnywhereFakeHandholds.Length != 0 && grabAnywhereFakeHandholds.Length <= 2)
{
CL_Handhold val = grabAnywhereFakeHandholds[^1];
CL_Handhold[] array = (CL_Handhold[])(object)new CL_Handhold[3];
Array.Copy(grabAnywhereFakeHandholds, array, grabAnywhereFakeHandholds.Length);
for (int i = grabAnywhereFakeHandholds.Length; i <= 2; i++)
{
CL_Handhold val2 = Object.Instantiate<CL_Handhold>(val, ((Component)val).transform.parent);
((Object)val2).name = ((Object)val).name + "_Third";
((Component)val2).gameObject.SetActive(false);
array[i] = val2;
}
player.grabAnywhereFakeHandholds = array;
}
Vector3[] array2 = GrabAnywherePosition.Invoke(player);
if (array2 != null && array2.Length <= 2)
{
Vector3[] array3 = (Vector3[])(object)new Vector3[3];
Array.Copy(array2, array3, array2.Length);
GrabAnywherePosition.Invoke(player) = array3;
}
Transform[] array4 = GrabAnywhereHit.Invoke(player);
if (array4 != null && array4.Length <= 2)
{
Transform[] array5 = (Transform[])(object)new Transform[3];
Array.Copy(array4, array5, array4.Length);
GrabAnywhereHit.Invoke(player) = array5;
}
}
}
internal static class ThirdHandInput
{
public const string FireButton = "Hand-Third";
public const string ThrowButton = "Hand-Third-Throw";
private static readonly ButtonInput Fire = new ButtonInput
{
name = "Hand-Third"
};
private static readonly ButtonInput Throw = new ButtonInput
{
name = "Hand-Third-Throw"
};
private static bool _latched;
private static bool _prevWant;
private static bool _holdSeen;
public static ButtonInput Get(string name)
{
if (!(name == "Hand-Third"))
{
return Throw;
}
return Fire;
}
public static bool IsThirdHandButton(string name)
{
return name?.StartsWith("Hand-Third") ?? false;
}
public static void Reset()
{
_latched = false;
_prevWant = false;
Set(Fire, pressed: false, down: false, up: false);
}
public static void Tick(bool inputAllowed, bool handIsHolding)
{
//IL_004b: Unknown result type (might be due to invalid IL or missing references)
ThirdHandControl value = ModConfig.Control.Value;
bool flag = value != ThirdHandControl.WheelScrollOnly;
bool flag2 = value != ThirdHandControl.WheelClickOnly;
if (!inputAllowed)
{
Set(Fire, _prevWant, down: false, up: false);
return;
}
bool flag3 = false;
if (flag)
{
flag3 = Input.GetMouseButton(2);
if (Input.GetMouseButtonDown(2))
{
_latched = false;
}
}
if (flag2)
{
float y = Input.mouseScrollDelta.y;
if (y > 0.01f)
{
_latched = true;
}
else if (y < -0.01f)
{
_latched = false;
}
}
else
{
_latched = false;
}
if (_latched && _prevWant && !handIsHolding && !Input.GetMouseButton(2) && _holdSeen)
{
_latched = false;
}
_holdSeen = handIsHolding;
bool flag4 = flag3 || _latched;
Set(Fire, flag4, flag4 && !_prevWant, !flag4 && _prevWant);
_prevWant = flag4;
}
private static void Set(ButtonInput b, bool pressed, bool down, bool up)
{
b.Pressed = pressed;
b.Down = down;
b.Up = up;
}
}
}