Decompiled source of Splice Spools v1.1.0

tony4twentys-Splice Spools.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Photon.Pun;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using Zorro.Core;
using Zorro.Core.Serizalization;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyTitle("Splice Spools")]
[assembly: AssemblyDescription("PEAK BepInEx plugin that splices matching inventory rope spools into one longer spool.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Splice Spools")]
[assembly: AssemblyCopyright("Copyright ©  2026")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("4006febd-7a9f-457c-865d-a3d4e2a97d25")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.0.0")]
[module: UnverifiableCode]
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}
namespace Splice_Spools
{
	[BepInPlugin("tony4twentys.Splice_Spools", "Splice_Spools", "1.1.0")]
	[BepInProcess("PEAK.exe")]
	public class SpliceSpoolsPlugin : BaseUnityPlugin
	{
		public const string PluginGuid = "tony4twentys.Splice_Spools";

		public const string PluginName = "Splice_Spools";

		public const string PluginVersion = "1.1.0";

		internal static ConfigEntry<KeyCode> SpliceKeyConfig;

		internal static ConfigEntry<bool> IncludeBackpackConfig;

		internal static ConfigEntry<float> LongPressSecondsConfig;

		internal static ConfigEntry<float> RememberedCutMetersConfig;

		private Harmony _harmony;

		private string _toastText;

		private float _toastUntil;

		private GUIStyle _toastStyle;

		private Coroutine _weightRoutine;

		internal static SpliceSpoolsPlugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		private void Awake()
		{
			//IL_00a3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Expected O, but got Unknown
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			SpliceKeyConfig = ((BaseUnityPlugin)this).Config.Bind<KeyCode>("Splice", "Splice Key", (KeyCode)99, "Tap to splice matching rope. Tap also opens the cut menu when there is nothing to combine. Hold to open the cut menu.");
			IncludeBackpackConfig = ((BaseUnityPlugin)this).Config.Bind<bool>("Splice", "Include Backpack", true, "When true, leftover rope in a worn backpack is included when splicing or cutting.");
			LongPressSecondsConfig = ((BaseUnityPlugin)this).Config.Bind<float>("Splice", "Long Press Seconds", 0.45f, "Hold the splice key this long to open the cut menu instead of combining.");
			RememberedCutMetersConfig = ((BaseUnityPlugin)this).Config.Bind<float>("Cut", "Remembered Cut Meters", 10f, "Last typed cut length for the remembered-amount button.");
			_harmony = new Harmony("tony4twentys.Splice_Spools");
			_harmony.PatchAll();
			CutMenu.EnsureCreated();
			((MonoBehaviour)this).StartCoroutine(WatchSpliceKey());
			((BaseUnityPlugin)this).Logger.LogInfo((object)($"Splice Spools loaded. Splice key: {SpliceKeyConfig.Value}. " + "Backpack: " + (IncludeBackpackConfig.Value ? "included" : "ignored") + "."));
		}

		private IEnumerator WatchSpliceKey()
		{
			while (true)
			{
				if (CutMenu.IsOpen)
				{
					yield return (object)new WaitWhile((Func<bool>)(() => CutMenu.IsOpen));
					continue;
				}
				yield return null;
				if (CutMenu.IsOpen || IsGameplayInputBlocked())
				{
					continue;
				}
				KeyCode key = (KeyCode)((SpliceKeyConfig != null) ? ((int)SpliceKeyConfig.Value) : 0);
				if ((int)key == 0 || !Input.GetKeyDown(key))
				{
					continue;
				}
				float hold = ((LongPressSecondsConfig != null) ? LongPressSecondsConfig.Value : 0.45f);
				if (hold < 0.15f)
				{
					hold = 0.15f;
				}
				float started = Time.unscaledTime;
				bool openedMenu = false;
				while (Input.GetKey(key))
				{
					if (!openedMenu && Time.unscaledTime - started >= hold)
					{
						openedMenu = true;
						CutMenu.Open();
					}
					yield return null;
				}
				if (!openedMenu && !IsGameplayInputBlocked())
				{
					TryTapSplice();
				}
			}
		}

		private void TryTapSplice()
		{
			Character localCharacter = Character.localCharacter;
			if (!((Object)(object)((localCharacter != null) ? localCharacter.player : null) == (Object)null) && localCharacter.IsLocal)
			{
				if (SpliceExecutor.TrySplice(localCharacter, out var message, out var hadMatchingPair))
				{
					ShowToast(message);
					((BaseUnityPlugin)this).Logger.LogInfo((object)message);
				}
				else if (hadMatchingPair)
				{
					ShowToast(message);
					((BaseUnityPlugin)this).Logger.LogInfo((object)message);
				}
				else
				{
					CutMenu.Open();
				}
			}
		}

		private void OnGUI()
		{
			//IL_008d: 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_00a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00be: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: 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_0046: 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_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			if (!string.IsNullOrEmpty(_toastText) && !(Time.unscaledTime > _toastUntil))
			{
				if (_toastStyle == null)
				{
					_toastStyle = new GUIStyle(GUI.skin.label)
					{
						alignment = (TextAnchor)4,
						fontSize = 16,
						fontStyle = (FontStyle)1
					};
				}
				Rect val = default(Rect);
				((Rect)(ref val))..ctor(((float)Screen.width - 520f) * 0.5f, 72f, 520f, 36f);
				Color color = GUI.color;
				GUI.color = new Color(0f, 0f, 0f, 0.55f);
				GUI.Box(val, GUIContent.none);
				GUI.color = Color.white;
				GUI.Label(val, _toastText, _toastStyle);
				GUI.color = color;
			}
		}

		private void OnDestroy()
		{
			CutMenu.DestroyInstance();
			if (_harmony != null)
			{
				_harmony.UnpatchSelf();
			}
			if ((Object)(object)Instance == (Object)(object)this)
			{
				Instance = null;
			}
			Log = null;
		}

		internal static void RequestWeightRefresh()
		{
			if (!((Object)(object)Instance == (Object)null) && Instance._weightRoutine == null)
			{
				Instance._weightRoutine = ((MonoBehaviour)Instance).StartCoroutine(Instance.RefreshWeightNextFrame());
			}
		}

		private IEnumerator RefreshWeightNextFrame()
		{
			yield return null;
			_weightRoutine = null;
			Character character = Character.localCharacter;
			if ((Object)(object)character?.refs?.items == (Object)null)
			{
				yield break;
			}
			try
			{
				character.refs.items.RefreshAllCharacterCarryWeightRPC();
			}
			catch (Exception ex)
			{
				ManualLogSource log = Log;
				if (log != null)
				{
					log.LogWarning((object)("Weight refresh skipped: " + ex.Message));
				}
			}
		}

		internal void ShowToast(string text)
		{
			_toastText = text;
			_toastUntil = Time.unscaledTime + 2.5f;
		}

		private static bool IsGameplayInputBlocked()
		{
			if (GUIManager.InPauseMenu)
			{
				return true;
			}
			GUIManager instance = GUIManager.instance;
			return (Object)(object)instance != (Object)null && instance.windowBlockingInput;
		}
	}
	internal static class GameItemIds
	{
		public const ushort AntiRopeSpoolItemId = 1;

		public const ushort RopeSpoolItemId = 65;
	}
	internal static class RopeSpoolLengthHelper
	{
		public const float MetersPerSegment = 0.25f;

		public const float OutOfRopeSegmentThreshold = 2f;

		public const float DefaultSpoolStartFuel = 60f;

		public const float SegmentEpsilon = 0.01f;

		public static float SegmentsToMeters(float segments)
		{
			return segments * 0.25f;
		}

		public static float MetersToSegments(float meters)
		{
			return meters / 0.25f;
		}

		public static float GetPlaceableSegments()
		{
			try
			{
				int maxSegments = Rope.MaxSegments;
				return (maxSegments > 0) ? ((float)maxSegments) : 40f;
			}
			catch
			{
				return 40f;
			}
		}

		public static float GetSpoolStartFuel(Item prefab)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return 60f;
			}
			RopeSpool component = ((Component)prefab).GetComponent<RopeSpool>();
			return ((Object)(object)component != (Object)null) ? component.ropeStartFuel : 60f;
		}

		public static float GetSpoolStartFuel(ItemSlot slot)
		{
			return GetSpoolStartFuel((slot != null) ? slot.prefab : null);
		}

		public static float GetRemainingSegments(Item prefab, ItemInstanceData data)
		{
			float spoolStartFuel = GetSpoolStartFuel(prefab);
			if (data == null)
			{
				return spoolStartFuel;
			}
			FloatItemData val = default(FloatItemData);
			if (data.TryGetDataEntry<FloatItemData>((DataEntryKey)10, ref val))
			{
				return Mathf.Max(0f, val.Value);
			}
			FloatItemData val2 = default(FloatItemData);
			if (data.TryGetDataEntry<FloatItemData>((DataEntryKey)11, ref val2))
			{
				return Mathf.Max(0f, val2.Value * spoolStartFuel);
			}
			return spoolStartFuel;
		}

		public static float GetRemainingSegments(ItemSlot slot)
		{
			if (slot == null || slot.IsEmpty())
			{
				return 0f;
			}
			return GetRemainingSegments(slot.prefab, slot.data);
		}

		public static float GetRemainingMeters(Item prefab, ItemInstanceData data)
		{
			return SegmentsToMeters(GetRemainingSegments(prefab, data));
		}

		public static int GetScaledCarryWeight(Item prefab, ItemInstanceData data)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return 0;
			}
			int carryWeight = prefab.CarryWeight;
			if (!SpliceInventory.IsRopeSpoolPrefab(prefab))
			{
				return carryWeight;
			}
			float spoolStartFuel = GetSpoolStartFuel(prefab);
			if (spoolStartFuel <= 0.01f)
			{
				return carryWeight;
			}
			float remainingSegments = GetRemainingSegments(prefab, data);
			return Mathf.RoundToInt((float)carryWeight * (remainingSegments / spoolStartFuel));
		}

		public static bool HasSplicableLength(float segments)
		{
			return segments > 2.01f;
		}
	}
	internal static class ItemDataHelper
	{
		public static void SetFloatEntry(ItemInstanceData data, DataEntryKey key, float value)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			FloatItemData val = default(FloatItemData);
			if (data.TryGetDataEntry<FloatItemData>(key, ref val))
			{
				val.Value = value;
				return;
			}
			FloatItemData val2 = new FloatItemData();
			((DataEntryValue)val2).Init();
			val2.Value = value;
			data.RegisterEntry<FloatItemData>(key, val2);
		}
	}
	internal static class SpliceInventory
	{
		private const byte TempInventorySlotId = 250;

		public static bool IsRopeSpoolPrefab(Item prefab)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return false;
			}
			if (prefab.itemID == 65 || prefab.itemID == 1)
			{
				return true;
			}
			if ((Object)(object)((Component)prefab).GetComponent<RopeShooter>() != (Object)null)
			{
				return false;
			}
			if ((Object)(object)((Component)prefab).GetComponent<RopeSpool>() != (Object)null)
			{
				return true;
			}
			string text = (((Object)(object)((Component)prefab).gameObject != (Object)null) ? ((Object)((Component)prefab).gameObject).name : string.Empty);
			return !string.IsNullOrEmpty(text) && text.IndexOf("Rope Spool", StringComparison.OrdinalIgnoreCase) >= 0;
		}

		public static bool IsAntiRopeSpoolPrefab(Item prefab)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return false;
			}
			if (prefab.itemID == 1)
			{
				return true;
			}
			RopeSpool component = ((Component)prefab).GetComponent<RopeSpool>();
			if ((Object)(object)component != (Object)null && component.isAntiRope)
			{
				return true;
			}
			string text = ((Object)((Component)prefab).gameObject).name ?? string.Empty;
			return text.IndexOf("Anti-Rope Spool", StringComparison.OrdinalIgnoreCase) >= 0;
		}

		public static List<ItemSlot> GetRopeSpoolSlots(Character character)
		{
			List<ItemSlot> list = new List<ItemSlot>();
			if ((Object)(object)((character != null) ? character.player : null) == (Object)null)
			{
				return list;
			}
			AppendRopeSlots(character.player.itemSlots, list);
			if (character.player.tempFullSlot != null && !character.player.tempFullSlot.IsEmpty() && IsRopeSpoolPrefab(character.player.tempFullSlot.prefab))
			{
				list.Add(character.player.tempFullSlot);
			}
			if (SpliceSpoolsPlugin.IncludeBackpackConfig.Value)
			{
				AppendBackpackRopeSlots(character, list);
			}
			return list;
		}

		private static void AppendRopeSlots(ItemSlot[] slots, List<ItemSlot> ropeSlots)
		{
			if (slots == null)
			{
				return;
			}
			foreach (ItemSlot val in slots)
			{
				if (val != null && !val.IsEmpty() && IsRopeSpoolPrefab(val.prefab))
				{
					ropeSlots.Add(val);
				}
			}
		}

		private static void AppendBackpackRopeSlots(Character character, List<ItemSlot> ropeSlots)
		{
			if (character.player.backpackSlot != null && !((ItemSlot)character.player.backpackSlot).IsEmpty())
			{
				ItemInstanceData data = ((ItemSlot)character.player.backpackSlot).data;
				BackpackData val = default(BackpackData);
				if (data != null && data.TryGetDataEntry<BackpackData>((DataEntryKey)7, ref val))
				{
					AppendRopeSlots(val.itemSlots, ropeSlots);
				}
			}
		}

		public static bool IsBackpackSlot(Character character, ItemSlot slot)
		{
			if ((Object)(object)((character != null) ? character.player : null) == (Object)null || slot == null || character.player.backpackSlot == null || ((ItemSlot)character.player.backpackSlot).IsEmpty())
			{
				return false;
			}
			ItemInstanceData data = ((ItemSlot)character.player.backpackSlot).data;
			BackpackData val = default(BackpackData);
			if (data == null || !data.TryGetDataEntry<BackpackData>((DataEntryKey)7, ref val) || val.itemSlots == null)
			{
				return false;
			}
			ItemSlot[] itemSlots = val.itemSlots;
			foreach (ItemSlot val2 in itemSlots)
			{
				if (val2 == slot)
				{
					return true;
				}
			}
			return false;
		}

		public static bool TryGetHeldSpool(Character character, out Item item, out RopeSpool spool)
		{
			object obj;
			if (character == null)
			{
				obj = null;
			}
			else
			{
				CharacterData data = character.data;
				obj = ((data != null) ? data.currentItem : null);
			}
			item = (Item)obj;
			spool = (((Object)(object)item != (Object)null) ? ((Component)item).GetComponent<RopeSpool>() : null);
			return (Object)(object)spool != (Object)null;
		}

		public static bool IsHeldSpoolBusy(Character character, bool isAntiRope)
		{
			if (!TryGetHeldSpool(character, out var item, out var spool))
			{
				return false;
			}
			if (spool.isAntiRope != isAntiRope)
			{
				return false;
			}
			RopeTier component = ((Component)item).GetComponent<RopeTier>();
			return item.isUsingPrimary || ((Object)(object)component != (Object)null && component.LookingToPlaceAnchor);
		}

		public static ItemSlot GetHeldSpoolSlot(Character character)
		{
			//IL_0063: 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)
			if (!TryGetHeldSpool(character, out var item, out var _))
			{
				return null;
			}
			ItemSlot val = FindSlotForItem(character, item);
			if (val != null)
			{
				return val;
			}
			Optionable<byte> valueOrDefault = (character.refs?.items?.currentSelectedSlot).GetValueOrDefault();
			if (valueOrDefault.IsSome)
			{
				ItemSlot itemSlot = character.player.GetItemSlot(valueOrDefault.Value);
				if (itemSlot != null && !itemSlot.IsEmpty() && IsRopeSpoolPrefab(itemSlot.prefab))
				{
					return itemSlot;
				}
			}
			if (character.player?.itemSlots != null)
			{
				ItemSlot[] itemSlots = character.player.itemSlots;
				foreach (ItemSlot val2 in itemSlots)
				{
					if (val2 != null && !val2.IsEmpty() && (Object)(object)val2.prefab != (Object)null && val2.prefab.itemID == item.itemID)
					{
						return val2;
					}
				}
			}
			return null;
		}

		public static float GetRemainingSegmentsLive(Character character, ItemSlot slot)
		{
			object obj;
			if (character == null)
			{
				obj = null;
			}
			else
			{
				CharacterData data = character.data;
				obj = ((data != null) ? data.currentItem : null);
			}
			Item val = (Item)obj;
			if ((Object)(object)val != (Object)null && SlotMatchesHeldItem(character, slot))
			{
				RopeSpool component = ((Component)val).GetComponent<RopeSpool>();
				if ((Object)(object)component != (Object)null)
				{
					return Mathf.Max(0f, component.RopeFuel);
				}
				return RopeSpoolLengthHelper.GetRemainingSegments(val, val.data);
			}
			return RopeSpoolLengthHelper.GetRemainingSegments(slot);
		}

		public static ItemSlot FindLongestSpool(Character character)
		{
			return FindLongestSpool(character, GetRopeSpoolSlots(character));
		}

		public static ItemSlot FindLongestSpool(Character character, IEnumerable<ItemSlot> slots)
		{
			ItemSlot val = null;
			float num = 0f;
			if (slots == null)
			{
				return null;
			}
			foreach (ItemSlot slot in slots)
			{
				if (slot != null && !slot.IsEmpty())
				{
					float remainingSegmentsLive = GetRemainingSegmentsLive(character, slot);
					if (RopeSpoolLengthHelper.HasSplicableLength(remainingSegmentsLive) && (val == null || remainingSegmentsLive > num))
					{
						val = slot;
						num = remainingSegmentsLive;
					}
				}
			}
			return val;
		}

		public static ItemSlot FindSlotForItem(Character character, Item item)
		{
			if ((Object)(object)((character != null) ? character.player : null) == (Object)null || (Object)(object)item == (Object)null)
			{
				return null;
			}
			if (item.data != null && item.data.guid != Guid.Empty)
			{
				ItemSlot val = FindSlotByGuid(character, item.data.guid);
				if (val != null)
				{
					return val;
				}
			}
			foreach (ItemSlot ropeSpoolSlot in GetRopeSpoolSlots(character))
			{
				if (SlotMatchesHeldItem(character, ropeSpoolSlot))
				{
					return ropeSpoolSlot;
				}
			}
			return null;
		}

		private static ItemSlot FindSlotByGuid(Character character, Guid guid)
		{
			ItemSlot val = FindSlotByGuid(character.player.itemSlots, guid);
			if (val != null)
			{
				return val;
			}
			if (SlotHasGuid(character.player.tempFullSlot, guid))
			{
				return character.player.tempFullSlot;
			}
			BackpackData val2 = default(BackpackData);
			if (character.player.backpackSlot != null && !((ItemSlot)character.player.backpackSlot).IsEmpty() && ((ItemSlot)character.player.backpackSlot).data != null && ((ItemSlot)character.player.backpackSlot).data.TryGetDataEntry<BackpackData>((DataEntryKey)7, ref val2))
			{
				return FindSlotByGuid(val2.itemSlots, guid);
			}
			return null;
		}

		private static ItemSlot FindSlotByGuid(ItemSlot[] slots, Guid guid)
		{
			if (slots == null)
			{
				return null;
			}
			foreach (ItemSlot val in slots)
			{
				if (SlotHasGuid(val, guid))
				{
					return val;
				}
			}
			return null;
		}

		private static bool SlotHasGuid(ItemSlot slot, Guid guid)
		{
			return slot != null && !slot.IsEmpty() && slot.data != null && slot.data.guid == guid;
		}

		public static bool SlotMatchesHeldItem(Character character, ItemSlot slot)
		{
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d7: Unknown result type (might be due to invalid IL or missing references)
			object obj;
			if (character == null)
			{
				obj = null;
			}
			else
			{
				CharacterData data = character.data;
				obj = ((data != null) ? data.currentItem : null);
			}
			Item val = (Item)obj;
			if ((Object)(object)val == (Object)null || slot == null || slot.IsEmpty() || (Object)(object)slot.prefab == (Object)null)
			{
				return false;
			}
			if (val.data != null && slot.data != null && val.data.guid != Guid.Empty && val.data.guid == slot.data.guid)
			{
				return true;
			}
			Optionable<byte> valueOrDefault = (character.refs?.items?.currentSelectedSlot).GetValueOrDefault();
			return valueOrDefault.IsSome && valueOrDefault.Value == slot.itemSlotID && slot.prefab.itemID == val.itemID;
		}

		public static void ApplyFuelToSlot(ItemSlot slot, float remaining, float startFuel)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			if (slot.data == null)
			{
				slot.data = new ItemInstanceData(Guid.NewGuid());
			}
			ItemDataHelper.SetFloatEntry(slot.data, (DataEntryKey)10, remaining);
			float value = ((startFuel > 0f) ? Mathf.Clamp01(remaining / startFuel) : 0f);
			ItemDataHelper.SetFloatEntry(slot.data, (DataEntryKey)11, value);
		}

		public static void ApplyFuelToHeldItem(Item item, float remaining, float startFuel)
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Expected O, but got Unknown
			if (!((Object)(object)item == (Object)null))
			{
				if (item.data == null)
				{
					item.data = new ItemInstanceData(Guid.NewGuid());
				}
				ItemDataHelper.SetFloatEntry(item.data, (DataEntryKey)10, remaining);
				RopeSpool component = ((Component)item).GetComponent<RopeSpool>();
				if ((Object)(object)component != (Object)null)
				{
					((ItemComponent)component).OnInstanceDataSet();
				}
				float useRemainingPercentage = ((startFuel > 0f) ? Mathf.Clamp01(remaining / startFuel) : 0f);
				item.SetUseRemainingPercentage(useRemainingPercentage);
			}
		}

		public static void EmptySlotLocal(Character character, ItemSlot slot)
		{
			if (slot == null)
			{
				return;
			}
			if (IsBackpackSlot(character, slot) || slot.itemSlotID == 250)
			{
				slot.EmptyOut();
				return;
			}
			ItemSlot itemSlot = character.player.GetItemSlot(slot.itemSlotID);
			if (itemSlot != null)
			{
				itemSlot.EmptyOut();
			}
		}

		public static void SyncInventory(Character character)
		{
			//IL_0071: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)((character != null) ? character.player : null) == (Object)null || !PhotonNetwork.InRoom)
			{
				return;
			}
			PhotonView component = ((Component)character.player).GetComponent<PhotonView>();
			if ((Object)(object)component == (Object)null)
			{
				return;
			}
			try
			{
				InventorySyncData val = default(InventorySyncData);
				((InventorySyncData)(ref val))..ctor(character.player.itemSlots, character.player.backpackSlot, character.player.tempFullSlot);
				byte[] array = IBinarySerializable.ToManagedArray<InventorySyncData>(val);
				component.RPC("SyncInventoryRPC", (RpcTarget)1, new object[2]
				{
					array,
					!PhotonNetwork.IsMasterClient
				});
				if (!PhotonNetwork.IsMasterClient)
				{
					component.RPC("SyncInventoryRPC", (RpcTarget)2, new object[2] { array, true });
				}
			}
			catch (Exception ex)
			{
				SpliceSpoolsPlugin.Log.LogWarning((object)("Inventory sync skipped: " + ex.Message));
			}
		}

		public static void SyncAfterChange(Character character, Item held)
		{
			SyncInventory(character);
			if ((Object)(object)held != (Object)null)
			{
				PushHeldItemData(held);
			}
			if ((Object)(object)character?.refs?.items != (Object)null)
			{
				character.refs.items.RefreshAllCharacterCarryWeightRPC();
			}
			if (((character == null) ? null : character.player?.itemsChangedAction) != null)
			{
				character.player.itemsChangedAction(character.player.itemSlots);
			}
		}

		public static int CountEmptyStorageSlots(ItemSlot ignore = null)
		{
			Character localCharacter = Character.localCharacter;
			if ((Object)(object)((localCharacter != null) ? localCharacter.player : null) == (Object)null)
			{
				return 0;
			}
			int num = 0;
			if (localCharacter.player.itemSlots != null)
			{
				ItemSlot[] itemSlots = localCharacter.player.itemSlots;
				foreach (ItemSlot val in itemSlots)
				{
					if (val != null && val != ignore && val.IsEmpty())
					{
						num++;
					}
				}
			}
			BackpackData val2 = default(BackpackData);
			if (!SpliceSpoolsPlugin.IncludeBackpackConfig.Value || localCharacter.player.backpackSlot == null || ((ItemSlot)localCharacter.player.backpackSlot).IsEmpty() || ((ItemSlot)localCharacter.player.backpackSlot).data == null || !((ItemSlot)localCharacter.player.backpackSlot).data.TryGetDataEntry<BackpackData>((DataEntryKey)7, ref val2) || val2.itemSlots == null)
			{
				return num;
			}
			ItemSlot[] itemSlots2 = val2.itemSlots;
			foreach (ItemSlot val3 in itemSlots2)
			{
				if (val3 != null && val3 != ignore && val3.IsEmpty())
				{
					num++;
				}
			}
			return num;
		}

		public static bool TryAddSpool(Character character, Item prefab, float segments, float startFuel, int dropIndex, out bool dropped)
		{
			dropped = false;
			if ((Object)(object)((character != null) ? character.player : null) == (Object)null || (Object)(object)prefab == (Object)null)
			{
				return false;
			}
			ItemInstanceData val = CreateSpoolData(segments, startFuel);
			if (character.player.itemSlots != null)
			{
				ItemSlot[] itemSlots = character.player.itemSlots;
				foreach (ItemSlot val2 in itemSlots)
				{
					if (val2 != null && val2.IsEmpty())
					{
						val2.SetItem(prefab, val);
						return true;
					}
				}
			}
			BackpackData val3 = default(BackpackData);
			if (SpliceSpoolsPlugin.IncludeBackpackConfig.Value && character.player.backpackSlot != null && !((ItemSlot)character.player.backpackSlot).IsEmpty() && ((ItemSlot)character.player.backpackSlot).data != null && ((ItemSlot)character.player.backpackSlot).data.TryGetDataEntry<BackpackData>((DataEntryKey)7, ref val3) && val3.itemSlots != null)
			{
				for (byte b = 0; b < val3.itemSlots.Length; b++)
				{
					ItemSlot val4 = val3.itemSlots[b];
					if (val4 != null && val4.IsEmpty())
					{
						val3.AddItem(prefab, val, b);
						return !val3.itemSlots[b].IsEmpty();
					}
				}
			}
			dropped = TryDropSpool(character, prefab, val, dropIndex);
			return dropped;
		}

		private static ItemInstanceData CreateSpoolData(float segments, float startFuel)
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Expected O, but got Unknown
			ItemInstanceData val = new ItemInstanceData(Guid.NewGuid());
			ItemInstanceDataHandler.AddInstanceData(val);
			ItemDataHelper.SetFloatEntry(val, (DataEntryKey)10, segments);
			float value = ((startFuel > 0f) ? Mathf.Clamp01(segments / startFuel) : 0f);
			ItemDataHelper.SetFloatEntry(val, (DataEntryKey)11, value);
			return val;
		}

		private static bool TryDropSpool(Character character, Item prefab, ItemInstanceData data, int dropIndex)
		{
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_004a: Unknown result type (might be due to invalid IL or missing references)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: 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_0100: Unknown result type (might be due to invalid IL or missing references)
			//IL_0113: Unknown result type (might be due to invalid IL or missing references)
			string text = PrefabObjectName(prefab);
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			Vector3 pos = DropPosition(character, 1.2f + (float)dropIndex * 0.15f, 0.35f, dropIndex);
			Quaternion rot = Quaternion.Euler(0f, (float)dropIndex * 35f, 0f);
			GameObject val = TryInstantiateItem(text, pos, rot);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			try
			{
				Item component = val.GetComponent<Item>();
				if ((Object)(object)component != (Object)null)
				{
					component.data = data;
					RopeSpool component2 = ((Component)component).GetComponent<RopeSpool>();
					if ((Object)(object)component2 != (Object)null)
					{
						((ItemComponent)component2).OnInstanceDataSet();
					}
				}
				PhotonView component3 = val.GetComponent<PhotonView>();
				if ((Object)(object)component3 != (Object)null)
				{
					component3.RPC("SetItemInstanceDataRPC", (RpcTarget)0, new object[1] { data });
					component3.RPC("SetKinematicRPC", (RpcTarget)3, new object[3]
					{
						false,
						val.transform.position,
						val.transform.rotation
					});
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = SpliceSpoolsPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Dropped spool but failed to write length: " + ex.Message));
				}
			}
			return true;
		}

		private static string PrefabObjectName(Item prefab)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return string.Empty;
			}
			string text = (((Object)(object)((Component)prefab).gameObject != (Object)null) ? ((Object)((Component)prefab).gameObject).name : ((Object)prefab).name);
			if (string.IsNullOrEmpty(text))
			{
				return string.Empty;
			}
			if (text.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				text = text.Substring(0, text.Length - "(Clone)".Length).TrimEnd(Array.Empty<char>());
			}
			return text;
		}

		private static Vector3 DropPosition(Character character, float forward, float up, int index)
		{
			//IL_0010: 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_001c: 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_002e: 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_003d: 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_0048: 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_005b: 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_006c: 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_007c: Unknown result type (might be due to invalid IL or missing references)
			//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_0093: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: 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_00a3: 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)
			try
			{
				Transform transform = character.GetBodypart((BodypartType)2).transform;
				return transform.position + transform.TransformVector(Vector3.forward * forward + Vector3.right * ((float)index * 0.4f)) + Vector3.up * up;
			}
			catch
			{
			}
			return character.Center + ((Component)character).transform.forward * forward + ((Component)character).transform.right * ((float)index * 0.4f) + Vector3.up * up;
		}

		private static GameObject TryInstantiateItem(string prefabName, Vector3 pos, Quaternion rot)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: 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)
			try
			{
				GameObject val = PhotonNetwork.InstantiateItemRoom(prefabName, pos, rot, true);
				if ((Object)(object)val != (Object)null)
				{
					return val;
				}
			}
			catch
			{
			}
			try
			{
				GameObject val2 = PhotonNetwork.Instantiate("0_Items/" + prefabName, pos, rot, (byte)0, (object[])null);
				if ((Object)(object)val2 != (Object)null)
				{
					return val2;
				}
			}
			catch
			{
			}
			try
			{
				return PhotonNetwork.Instantiate(prefabName, pos, rot, (byte)0, (object[])null);
			}
			catch (Exception ex)
			{
				ManualLogSource log = SpliceSpoolsPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Could not drop rope spool '" + prefabName + "': " + ex.Message));
				}
				return null;
			}
		}

		public static void PushHeldItemData(Item item)
		{
			if (item?.data == null || !PhotonNetwork.InRoom)
			{
				return;
			}
			PhotonView component = ((Component)item).GetComponent<PhotonView>();
			if ((Object)(object)component == (Object)null || (!component.IsMine && !PhotonNetwork.IsMasterClient))
			{
				return;
			}
			try
			{
				component.RPC("SetItemInstanceDataRPC", (RpcTarget)1, new object[1] { item.data });
			}
			catch (Exception ex)
			{
				SpliceSpoolsPlugin.Log.LogWarning((object)("Held spool sync skipped: " + ex.Message));
			}
		}
	}
	internal static class SpliceExecutor
	{
		public static bool TrySplice(Character character, out string message, out bool hadMatchingPair)
		{
			CollectSplicableGroups(character, out var normal, out var anti);
			hadMatchingPair = normal.Count >= 2 || anti.Count >= 2;
			bool flag = false;
			bool flag2 = false;
			float combinedMeters = 0f;
			float combinedMeters2 = 0f;
			int spoolCount = 0;
			int spoolCount2 = 0;
			if (normal.Count >= 2)
			{
				if (SpliceInventory.IsHeldSpoolBusy(character, isAntiRope: false))
				{
					message = "Finish placing your rope before splicing.";
					return false;
				}
				flag = TrySpliceGroup(character, normal, isAntiRope: false, out combinedMeters, out spoolCount);
			}
			if (anti.Count >= 2)
			{
				if (SpliceInventory.IsHeldSpoolBusy(character, isAntiRope: true))
				{
					if (flag)
					{
						SpliceInventory.SyncAfterChange(character, null);
						message = BuildResultMessage(splicedNormal: true, spoolCount, combinedMeters, splicedAnti: false, 0, 0f) + " Anti-grav spool is in use.";
						return true;
					}
					message = "Finish placing your anti-grav rope before splicing.";
					return false;
				}
				flag2 = TrySpliceGroup(character, anti, isAntiRope: true, out combinedMeters2, out spoolCount2);
			}
			if (!flag && !flag2)
			{
				message = ((normal.Count + anti.Count == 0) ? "No leftover rope spools to splice." : "Need at least two matching rope spools to splice.");
				return false;
			}
			SpliceInventory.TryGetHeldSpool(character, out var item, out var _);
			SpliceInventory.SyncAfterChange(character, item);
			message = BuildResultMessage(flag, spoolCount, combinedMeters, flag2, spoolCount2, combinedMeters2);
			return true;
		}

		private static void CollectSplicableGroups(Character character, out List<ItemSlot> normal, out List<ItemSlot> anti)
		{
			normal = new List<ItemSlot>();
			anti = new List<ItemSlot>();
			foreach (ItemSlot ropeSpoolSlot in SpliceInventory.GetRopeSpoolSlots(character))
			{
				AddSplicable(character, ropeSpoolSlot, normal, anti);
			}
			ItemSlot heldSpoolSlot = SpliceInventory.GetHeldSpoolSlot(character);
			if (heldSpoolSlot != null)
			{
				AddSplicable(character, heldSpoolSlot, normal, anti);
			}
		}

		private static void AddSplicable(Character character, ItemSlot slot, List<ItemSlot> normal, List<ItemSlot> anti)
		{
			if (slot == null || slot.IsEmpty() || ContainsSlot(normal, slot) || ContainsSlot(anti, slot))
			{
				return;
			}
			float remainingSegmentsLive = SpliceInventory.GetRemainingSegmentsLive(character, slot);
			if (RopeSpoolLengthHelper.HasSplicableLength(remainingSegmentsLive))
			{
				if (SpliceInventory.IsAntiRopeSpoolPrefab(slot.prefab))
				{
					anti.Add(slot);
				}
				else
				{
					normal.Add(slot);
				}
			}
		}

		private static bool ContainsSlot(List<ItemSlot> slots, ItemSlot slot)
		{
			for (int i = 0; i < slots.Count; i++)
			{
				if (slots[i] == slot)
				{
					return true;
				}
			}
			return false;
		}

		private static bool TrySpliceGroup(Character character, List<ItemSlot> slots, bool isAntiRope, out float combinedMeters, out int spoolCount)
		{
			combinedMeters = 0f;
			spoolCount = slots.Count;
			if (slots.Count < 2)
			{
				return false;
			}
			ItemSlot val = ChooseTargetSlot(character, slots, isAntiRope);
			if (val == null)
			{
				return false;
			}
			float num = 0f;
			foreach (ItemSlot slot in slots)
			{
				num += SpliceInventory.GetRemainingSegmentsLive(character, slot);
			}
			if (!RopeSpoolLengthHelper.HasSplicableLength(num))
			{
				return false;
			}
			float spoolStartFuel = RopeSpoolLengthHelper.GetSpoolStartFuel(val);
			SpliceInventory.ApplyFuelToSlot(val, num, spoolStartFuel);
			if (SpliceInventory.SlotMatchesHeldItem(character, val))
			{
				SpliceInventory.ApplyFuelToHeldItem(character.data.currentItem, num, spoolStartFuel);
			}
			foreach (ItemSlot slot2 in slots)
			{
				if (slot2 != val)
				{
					SpliceInventory.EmptySlotLocal(character, slot2);
				}
			}
			combinedMeters = RopeSpoolLengthHelper.SegmentsToMeters(num);
			return true;
		}

		private static ItemSlot ChooseTargetSlot(Character character, List<ItemSlot> slots, bool isAntiRope)
		{
			ItemSlot heldSpoolSlot = SpliceInventory.GetHeldSpoolSlot(character);
			if (heldSpoolSlot != null && SpliceInventory.IsAntiRopeSpoolPrefab(heldSpoolSlot.prefab) == isAntiRope)
			{
				foreach (ItemSlot slot in slots)
				{
					if (slot == heldSpoolSlot || SpliceInventory.SlotMatchesHeldItem(character, slot))
					{
						return slot;
					}
				}
			}
			foreach (ItemSlot slot2 in slots)
			{
				if (SpliceInventory.SlotMatchesHeldItem(character, slot2) && SpliceInventory.IsAntiRopeSpoolPrefab(slot2.prefab) == isAntiRope)
				{
					return slot2;
				}
			}
			return SpliceInventory.FindLongestSpool(character, slots) ?? slots[0];
		}

		private static string BuildResultMessage(bool splicedNormal, int normalCount, float normalMeters, bool splicedAnti, int antiCount, float antiMeters)
		{
			if (splicedNormal && splicedAnti)
			{
				return $"Spliced {normalCount} rope spools into {normalMeters:0.##}m and {antiCount} anti-grav spools into {antiMeters:0.##}m.";
			}
			if (splicedAnti)
			{
				return $"Spliced {antiCount} anti-grav spools into {antiMeters:0.##}m.";
			}
			return $"Spliced {normalCount} rope spools into {normalMeters:0.##}m.";
		}
	}
	internal sealed class CutHoverTarget : MonoBehaviour, IPointerEnterHandler, IEventSystemHandler, IPointerExitHandler
	{
		internal Func<float> GetSegments;

		public void OnPointerEnter(PointerEventData eventData)
		{
			if (GetSegments != null)
			{
				CutMenu.SetHover(GetSegments());
			}
		}

		public void OnPointerExit(PointerEventData eventData)
		{
			CutMenu.ClearHover();
		}
	}
	internal sealed class CutButtonBind : MonoBehaviour
	{
		internal float Segments;

		internal bool IsPreset;

		internal string LastLabel;

		internal Button Button;

		internal TextMeshProUGUI Label;

		private void Awake()
		{
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			Button = ((Component)this).GetComponent<Button>();
			Label = ((Component)this).GetComponentInChildren<TextMeshProUGUI>(true);
			CutHoverTarget component = ((Component)this).GetComponent<CutHoverTarget>();
			if ((Object)(object)component != (Object)null)
			{
				component.GetSegments = () => Segments;
			}
			if ((Object)(object)Button != (Object)null)
			{
				((UnityEvent)Button.onClick).AddListener((UnityAction)delegate
				{
					CutMenu.ClickBound(this);
				});
			}
		}
	}
	internal sealed class CutMenu : MonoBehaviour
	{
		private const int SortOrder = 9400;

		private const float ButtonHeight = 38f;

		private const float SourceCheckSeconds = 0.35f;

		private static CutMenu _instance;

		private static float _hoverSegments = -1f;

		private bool _visible;

		private bool _built;

		private CursorLockMode _prevLock;

		private bool _prevCursorVisible;

		private Vector2 _frozenLookValues;

		private bool _hasFrozenLook;

		private Coroutine _menuRoutine;

		private ItemSlot _source;

		private bool _layoutDirty;

		private bool _previewDirty;

		private float _placeable;

		private float _cachedRemaining = float.MinValue;

		private float _cachedVanilla = float.MinValue;

		private float _cachedHover = float.MinValue;

		private int _cachedEmpty = int.MinValue;

		private string _cachedCustom = null;

		private string _cachedRemembered = null;

		private string _cachedTitle;

		private string _cachedStatus;

		private string _cachedPreview;

		private TextMeshProUGUI _title;

		private TextMeshProUGUI _status;

		private TextMeshProUGUI _preview;

		private Button _fullVanilla;

		private Button _halfVanilla;

		private Button _maxPlaceable;

		private Button _halfCurrent;

		private Button _quarterCurrent;

		private Button _thirdCurrent;

		private Button _remembered;

		private Button _custom;

		private TMP_InputField _rememberedField;

		private TMP_InputField _customField;

		private string _customText = string.Empty;

		public static bool IsOpen => (Object)(object)_instance != (Object)null && _instance._visible && ((Component)_instance).gameObject.activeInHierarchy;

		public static void EnsureCreated()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Expected O, but got Unknown
			if (!((Object)(object)_instance != (Object)null))
			{
				GameObject val = new GameObject("SpliceSpools_CutMenu");
				Object.DontDestroyOnLoad((Object)(object)val);
				_instance = val.AddComponent<CutMenu>();
				val.SetActive(false);
			}
		}

		public static void Open()
		{
			EnsureCreated();
			_instance.OpenInternal();
		}

		public static void Close()
		{
			if ((Object)(object)_instance != (Object)null)
			{
				_instance.CloseInternal();
			}
		}

		public static void DestroyInstance()
		{
			if (!((Object)(object)_instance == (Object)null))
			{
				_instance.CloseInternal();
				Object.Destroy((Object)(object)((Component)_instance).gameObject);
				_instance = null;
			}
		}

		public static void SetHover(float segments)
		{
			if (!(Mathf.Abs(_hoverSegments - segments) <= 0.01f))
			{
				_hoverSegments = segments;
				if ((Object)(object)_instance != (Object)null)
				{
					_instance._previewDirty = true;
				}
			}
		}

		public static void ClearHover()
		{
			if (!(_hoverSegments < 0f))
			{
				_hoverSegments = -1f;
				if ((Object)(object)_instance != (Object)null)
				{
					_instance._previewDirty = true;
				}
			}
		}

		internal static void ClickBound(CutButtonBind bind)
		{
			if (!((Object)(object)_instance == (Object)null) && !((Object)(object)bind == (Object)null))
			{
				_instance.TryCutClicked(bind.Segments);
			}
		}

		private void OpenInternal()
		{
			//IL_006b: 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)
			Character localCharacter = Character.localCharacter;
			if (!CutExecutor.TryGetSource(localCharacter, out var source, out var message))
			{
				SpliceSpoolsPlugin.Instance?.ShowToast(message);
				ManualLogSource log = SpliceSpoolsPlugin.Log;
				if (log != null)
				{
					log.LogInfo((object)message);
				}
				return;
			}
			CutUi.EnsureEventSystem();
			Build();
			_source = source;
			_visible = true;
			((Component)this).gameObject.SetActive(true);
			_prevLock = Cursor.lockState;
			_prevCursorVisible = Cursor.visible;
			Cursor.lockState = (CursorLockMode)0;
			Cursor.visible = true;
			ApplyMenuInputBlock(block: true);
			CaptureFrozenLook();
			if ((Object)(object)_customField != (Object)null)
			{
				_customField.text = _customText ?? string.Empty;
			}
			if ((Object)(object)_rememberedField != (Object)null && SpliceSpoolsPlugin.RememberedCutMetersConfig != null)
			{
				_rememberedField.text = SpliceSpoolsPlugin.RememberedCutMetersConfig.Value.ToString("0.##", CultureInfo.InvariantCulture);
			}
			_placeable = RopeSpoolLengthHelper.GetPlaceableSegments();
			InvalidateCache();
			Refresh(force: true);
			if (_menuRoutine == null)
			{
				_menuRoutine = ((MonoBehaviour)this).StartCoroutine(MenuRoutine());
			}
		}

		private void CloseInternal()
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			if (_visible)
			{
				_visible = false;
				_hoverSegments = -1f;
				_hasFrozenLook = false;
				_source = null;
				_menuRoutine = null;
				if ((Object)(object)_rememberedField != (Object)null)
				{
					_rememberedField.DeactivateInputField(false);
				}
				if ((Object)(object)_customField != (Object)null)
				{
					_customField.DeactivateInputField(false);
				}
				CutUi.ReleaseUiFocus();
				ApplyMenuInputBlock(block: false);
				Cursor.lockState = _prevLock;
				Cursor.visible = _prevCursorVisible;
				((Component)this).gameObject.SetActive(false);
			}
		}

		private IEnumerator MenuRoutine()
		{
			float sourceCheck = 0f;
			while (_visible)
			{
				if (Input.GetKeyDown((KeyCode)27) || (SpliceSpoolsPlugin.SpliceKeyConfig != null && Input.GetKeyDown(SpliceSpoolsPlugin.SpliceKeyConfig.Value)))
				{
					CloseInternal();
					yield break;
				}
				sourceCheck += Time.unscaledDeltaTime;
				if (sourceCheck >= 0.35f)
				{
					sourceCheck = 0f;
					if (!TryRefreshSource())
					{
						yield break;
					}
				}
				if (_layoutDirty)
				{
					Refresh(force: true);
				}
				else if (_previewDirty)
				{
					RefreshPreview();
				}
				yield return null;
			}
			_menuRoutine = null;
		}

		private void LateUpdate()
		{
			if (_visible)
			{
				HoldFrozenLook();
			}
		}

		private bool TryRefreshSource()
		{
			if (!CutExecutor.TryGetSource(Character.localCharacter, out var source, out var message))
			{
				CloseInternal();
				SpliceSpoolsPlugin.Instance?.ShowToast(message);
				return false;
			}
			if (_source != source)
			{
				_source = source;
				_layoutDirty = true;
				return true;
			}
			float remainingSegmentsLive = SpliceInventory.GetRemainingSegmentsLive(Character.localCharacter, source);
			if (Mathf.Abs(remainingSegmentsLive - _cachedRemaining) > 0.05f)
			{
				_layoutDirty = true;
			}
			return true;
		}

		private void Build()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: 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_0064: Expected O, but got Unknown
			//IL_00fa: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f1: Expected O, but got Unknown
			if (!_built)
			{
				CutUi.CreateOverlayCanvas(((Component)this).gameObject, 9400);
				RectTransform val = CutUi.CreatePanel(((Component)this).transform, "Panel", new Vector2(560f, 720f), CutUi.PanelColor);
				CutUi.AddVertical(((Component)val).gameObject, 8f, new RectOffset(22, 22, 18, 18));
				_title = CutUi.CreateLabel((Transform)(object)val, "Title", "Cut Rope", 26f, (FontStyles)1, (TextAlignmentOptions)514);
				AddFixedHeight(((Component)_title).gameObject, 34f);
				_status = CutUi.CreateLabel((Transform)(object)val, "Status", string.Empty, 16f, (FontStyles)0, (TextAlignmentOptions)514);
				AddFixedHeight(((Component)_status).gameObject, 48f);
				_preview = CutUi.CreateLabel((Transform)(object)val, "Preview", "Hover a cut to preview.", 16f, (FontStyles)0, (TextAlignmentOptions)514);
				((Graphic)_preview).color = CutUi.CreamDim;
				AddFixedHeight(((Component)_preview).gameObject, 72f);
				_fullVanilla = AddCutButton((Transform)(object)val, "Full vanilla spool", preset: true);
				_halfVanilla = AddCutButton((Transform)(object)val, "Half vanilla spool", preset: true);
				_maxPlaceable = AddCutButton((Transform)(object)val, "Max placeable length", preset: true);
				_halfCurrent = AddCutButton((Transform)(object)val, "Half of this spool", preset: true);
				_quarterCurrent = AddCutButton((Transform)(object)val, "1/4 of this spool", preset: true);
				_thirdCurrent = AddCutButton((Transform)(object)val, "1/3 of this spool", preset: true);
				AddAmountRow((Transform)(object)val, "Remembered meters", "Cut remembered", remembered: true, out _rememberedField, out _remembered);
				AddAmountRow((Transform)(object)val, "Custom meters", "Cut custom", remembered: false, out _customField, out _custom);
				Button val2 = CutUi.CreateButton((Transform)(object)val, "Close", new UnityAction(CloseInternal), new Vector2(200f, 38f));
				((Component)val2).transform.SetAsLastSibling();
				_built = true;
			}
		}

		private Button AddCutButton(Transform parent, string label, bool preset)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			Button val = CutUi.CreateButton(parent, label, null, new Vector2(500f, 38f));
			((Component)val).gameObject.AddComponent<CutHoverTarget>();
			CutButtonBind cutButtonBind = ((Component)val).gameObject.AddComponent<CutButtonBind>();
			cutButtonBind.IsPreset = preset;
			return val;
		}

		private void AddAmountRow(Transform parent, string placeholder, string buttonLabel, bool remembered, out TMP_InputField field, out Button button)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Expected O, but got Unknown
			//IL_00e2: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(remembered ? "RememberedRow" : "CustomRow", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			LayoutElement val2 = val.AddComponent<LayoutElement>();
			val2.minHeight = 38f;
			val2.preferredHeight = 38f;
			CutUi.AddHorizontal(val, 8f);
			string initial = string.Empty;
			if (remembered && SpliceSpoolsPlugin.RememberedCutMetersConfig != null)
			{
				initial = SpliceSpoolsPlugin.RememberedCutMetersConfig.Value.ToString("0.##", CultureInfo.InvariantCulture);
			}
			UnityAction<string> onChanged = (remembered ? new UnityAction<string>(OnRememberedChanged) : new UnityAction<string>(OnCustomChanged));
			field = CutUi.CreateNumberField(val.transform, placeholder, initial, onChanged);
			button = CutUi.CreateButton(val.transform, buttonLabel, null, new Vector2(180f, 38f));
			LayoutElement component = ((Component)button).GetComponent<LayoutElement>();
			component.flexibleWidth = 0f;
			component.preferredWidth = 180f;
			component.minWidth = 180f;
			((Component)button).gameObject.AddComponent<CutHoverTarget>();
			CutButtonBind cutButtonBind = ((Component)button).gameObject.AddComponent<CutButtonBind>();
			cutButtonBind.IsPreset = false;
		}

		private static void AddFixedHeight(GameObject go, float height)
		{
			LayoutElement val = go.GetComponent<LayoutElement>() ?? go.AddComponent<LayoutElement>();
			val.minHeight = height;
			val.preferredHeight = height;
		}

		private void OnRememberedChanged(string value)
		{
			if (TryParseMeters(value, out var meters) && SpliceSpoolsPlugin.RememberedCutMetersConfig != null)
			{
				SpliceSpoolsPlugin.RememberedCutMetersConfig.Value = meters;
			}
			_layoutDirty = true;
		}

		private void OnCustomChanged(string value)
		{
			_customText = value ?? string.Empty;
			_layoutDirty = true;
		}

		private void InvalidateCache()
		{
			_cachedRemaining = float.MinValue;
			_cachedVanilla = float.MinValue;
			_cachedHover = float.MinValue;
			_cachedEmpty = int.MinValue;
			_cachedCustom = null;
			_cachedRemembered = null;
			_layoutDirty = true;
			_previewDirty = true;
		}

		private void Refresh(bool force)
		{
			if (_source != null && !_source.IsEmpty())
			{
				float remainingSegmentsLive = SpliceInventory.GetRemainingSegmentsLive(Character.localCharacter, _source);
				float spoolStartFuel = RopeSpoolLengthHelper.GetSpoolStartFuel(_source);
				string text = (((Object)(object)_rememberedField != (Object)null) ? _rememberedField.text : string.Empty);
				if (force || Mathf.Abs(remainingSegmentsLive - _cachedRemaining) > 0.01f || Mathf.Abs(spoolStartFuel - _cachedVanilla) > 0.01f || !string.Equals(_cachedCustom, _customText, StringComparison.Ordinal) || !string.Equals(_cachedRemembered, text, StringComparison.Ordinal))
				{
					_cachedRemaining = remainingSegmentsLive;
					_cachedVanilla = spoolStartFuel;
					_cachedCustom = _customText;
					_cachedRemembered = text;
					WriteStatus(remainingSegmentsLive, spoolStartFuel);
					WriteButtons(remainingSegmentsLive, spoolStartFuel);
					_previewDirty = true;
				}
				RefreshPreview();
				_layoutDirty = false;
			}
		}

		private void WriteStatus(float remaining, float vanilla)
		{
			string text = (SpliceInventory.IsAntiRopeSpoolPrefab(_source.prefab) ? "Anti-grav rope" : "Rope");
			string text2 = "Cut " + text;
			string text3 = string.Format(CultureInfo.InvariantCulture, "{0}  •  {1:0.##}m now\nVanilla full {2:0.##}m  •  Placeable {3:0.##}m", text, RopeSpoolLengthHelper.SegmentsToMeters(remaining), RopeSpoolLengthHelper.SegmentsToMeters(vanilla), RopeSpoolLengthHelper.SegmentsToMeters(_placeable));
			if (_cachedTitle != text2)
			{
				_cachedTitle = text2;
				((TMP_Text)_title).text = text2;
			}
			if (_cachedStatus != text3)
			{
				_cachedStatus = text3;
				((TMP_Text)_status).text = text3;
			}
		}

		private void WriteButtons(float remaining, float vanilla)
		{
			ApplyPreset(_fullVanilla, "Full vanilla spool ({0:0.##}m)", vanilla, remaining);
			ApplyPreset(_halfVanilla, "Half vanilla spool ({0:0.##}m)", vanilla * 0.5f, remaining);
			ApplyPreset(_maxPlaceable, "Max placeable ({0:0.##}m)", _placeable, remaining);
			ApplyPreset(_halfCurrent, "Half of this spool ({0:0.##}m)", remaining * 0.5f, remaining);
			ApplyPreset(_quarterCurrent, "1/4 of this spool ({0:0.##}m)", remaining * 0.25f, remaining);
			ApplyPreset(_thirdCurrent, "1/3 of this spool ({0:0.##}m)", remaining / 3f, remaining);
			float meters = 0f;
			bool flag = TryParseMeters(_cachedRemembered, out meters);
			ApplyTyped(_remembered, flag ? RopeSpoolLengthHelper.MetersToSegments(meters) : (-1f), remaining);
			float meters2 = 0f;
			bool flag2 = TryParseMeters(_customText, out meters2);
			ApplyTyped(_custom, flag2 ? RopeSpoolLengthHelper.MetersToSegments(meters2) : (-1f), remaining);
		}

		private void RefreshPreview()
		{
			if (_source == null || _source.IsEmpty() || (Object)(object)_preview == (Object)null)
			{
				_previewDirty = false;
				return;
			}
			float num = ResolvePreviewSegments();
			int num2 = SpliceInventory.CountEmptyStorageSlots(_source);
			if (!_previewDirty && Mathf.Abs(num - _cachedHover) <= 0.01f && num2 == _cachedEmpty)
			{
				_previewDirty = false;
				return;
			}
			_cachedHover = num;
			_cachedEmpty = num2;
			string text = CutExecutor.BuildPreview(_source, num, _cachedRemaining, _cachedVanilla, num2);
			if (_cachedPreview != text)
			{
				_cachedPreview = text;
				((TMP_Text)_preview).text = text;
			}
			_previewDirty = false;
		}

		private float ResolvePreviewSegments()
		{
			if (_hoverSegments >= 0f)
			{
				return _hoverSegments;
			}
			if ((Object)(object)EventSystem.current == (Object)null)
			{
				return -1f;
			}
			GameObject currentSelectedGameObject = EventSystem.current.currentSelectedGameObject;
			if ((Object)(object)currentSelectedGameObject != (Object)null && (Object)(object)_rememberedField != (Object)null && currentSelectedGameObject.transform.IsChildOf(((Component)_rememberedField).transform) && TryParseMeters(_cachedRemembered, out var meters))
			{
				return RopeSpoolLengthHelper.MetersToSegments(meters);
			}
			if ((Object)(object)currentSelectedGameObject != (Object)null && (Object)(object)_customField != (Object)null && currentSelectedGameObject.transform.IsChildOf(((Component)_customField).transform) && TryParseMeters(_customText, out meters))
			{
				return RopeSpoolLengthHelper.MetersToSegments(meters);
			}
			return -1f;
		}

		private static void ApplyPreset(Button button, string labelFormat, float cutSegments, float remaining)
		{
			CutButtonBind component = ((Component)button).GetComponent<CutButtonBind>();
			bool flag = CutExecutor.CanCut(remaining, cutSegments);
			if ((Object)(object)component != (Object)null)
			{
				component.Segments = cutSegments;
			}
			if (((Component)button).gameObject.activeSelf != flag)
			{
				((Component)button).gameObject.SetActive(flag);
			}
			if (flag)
			{
				string text = string.Format(CultureInfo.InvariantCulture, labelFormat, RopeSpoolLengthHelper.SegmentsToMeters(cutSegments));
				if ((Object)(object)component != (Object)null && (Object)(object)component.Label != (Object)null && component.LastLabel != text)
				{
					component.LastLabel = text;
					((TMP_Text)component.Label).text = text;
				}
			}
		}

		private static void ApplyTyped(Button button, float cutSegments, float remaining)
		{
			CutButtonBind component = ((Component)button).GetComponent<CutButtonBind>();
			if ((Object)(object)component != (Object)null)
			{
				component.Segments = cutSegments;
			}
			bool flag = cutSegments > 0f && CutExecutor.CanCut(remaining, cutSegments);
			if (((Selectable)button).interactable != flag)
			{
				((Selectable)button).interactable = flag;
			}
		}

		private void TryCutClicked(float cutSegments)
		{
			Character localCharacter = Character.localCharacter;
			string message;
			bool flag = CutExecutor.TryCut(localCharacter, cutSegments, out message);
			SpliceSpoolsPlugin.Instance?.ShowToast(message);
			ManualLogSource log = SpliceSpoolsPlugin.Log;
			if (log != null)
			{
				log.LogInfo((object)message);
			}
			if (flag && TryRefreshSource())
			{
				InvalidateCache();
				Refresh(force: true);
			}
		}

		private static bool TryParseMeters(string text, out float meters)
		{
			meters = 0f;
			if (string.IsNullOrEmpty(text))
			{
				return false;
			}
			return float.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out meters) || float.TryParse(text, NumberStyles.Float, CultureInfo.CurrentCulture, out meters);
		}

		private void CaptureFrozenLook()
		{
			//IL_0027: 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)
			Character localCharacter = Character.localCharacter;
			if (!((Object)(object)localCharacter?.data == (Object)null))
			{
				_frozenLookValues = localCharacter.data.lookValues;
				_hasFrozenLook = true;
			}
		}

		private void HoldFrozenLook()
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Unknown result type (might be due to invalid IL or missing references)
			if (_hasFrozenLook)
			{
				Character localCharacter = Character.localCharacter;
				if (!((Object)(object)localCharacter?.data == (Object)null))
				{
					localCharacter.data.lookValues = _frozenLookValues;
				}
			}
		}

		internal static void ApplyMenuInputBlock(bool block)
		{
			GUIManager instance = GUIManager.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return;
			}
			try
			{
				instance.windowBlockingInput = block;
				instance.windowShowingCursor = block;
			}
			catch
			{
			}
		}
	}
	internal static class CutExecutor
	{
		public static bool TryGetSource(Character character, out ItemSlot source, out string message)
		{
			source = null;
			if ((Object)(object)((character != null) ? character.player : null) == (Object)null || !character.IsLocal)
			{
				message = "No local scout to cut rope from.";
				return false;
			}
			if (SpliceInventory.TryGetHeldSpool(character, out var _, out var spool))
			{
				if (SpliceInventory.IsHeldSpoolBusy(character, spool.isAntiRope))
				{
					message = "Finish placing your rope before cutting.";
					return false;
				}
				source = SpliceInventory.GetHeldSpoolSlot(character);
				if (source != null && RopeSpoolLengthHelper.HasSplicableLength(SpliceInventory.GetRemainingSegmentsLive(character, source)))
				{
					message = string.Empty;
					return true;
				}
			}
			source = SpliceInventory.FindLongestSpool(character);
			if (source == null)
			{
				message = "Hold a rope spool, or keep one in your inventory, to cut.";
				return false;
			}
			message = string.Empty;
			return true;
		}

		public static bool CanCut(float remaining, float cutSegments)
		{
			if (cutSegments <= 2.01f)
			{
				return false;
			}
			float num = remaining - cutSegments;
			return num > 2.01f;
		}

		public static string BuildPreview(ItemSlot source, float cutSegments, float remaining, float vanilla, int empty)
		{
			if (source == null || source.IsEmpty())
			{
				return "No rope selected.";
			}
			if (cutSegments <= 0f)
			{
				return "Hover or select a cut to preview the result.";
			}
			if (!CanCut(remaining, cutSegments))
			{
				return "That cut is not possible. Leave more than a few meters on this spool.";
			}
			List<float> list = PackPieces(cutSegments, vanilla);
			int num = Mathf.Max(0, list.Count - empty);
			float segments = remaining - cutSegments;
			string text = ((num > 0) ? string.Format(CultureInfo.InvariantCulture, "{0} drop at your feet", num) : "all fit in inventory");
			return string.Format(CultureInfo.InvariantCulture, "After cut: {0:0.##}m left on this spool\nCreates {1} spool{2} ({3})\n{4} free slot{5}  •  {6}", RopeSpoolLengthHelper.SegmentsToMeters(segments), list.Count, (list.Count == 1) ? string.Empty : "s", FormatPieces(list), empty, (empty == 1) ? string.Empty : "s", text);
		}

		public static bool TryCut(Character character, float cutSegments, out string message)
		{
			if (!TryGetSource(character, out var source, out message))
			{
				return false;
			}
			float remainingSegmentsLive = SpliceInventory.GetRemainingSegmentsLive(character, source);
			float spoolStartFuel = RopeSpoolLengthHelper.GetSpoolStartFuel(source);
			if (!CanCut(remainingSegmentsLive, cutSegments))
			{
				message = "That cut would leave too little rope on this spool.";
				return false;
			}
			List<float> list = PackPieces(cutSegments, spoolStartFuel);
			if (list.Count == 0)
			{
				message = "Nothing to cut.";
				return false;
			}
			float num = 0f;
			int num2 = 0;
			int num3 = 0;
			for (int i = 0; i < list.Count; i++)
			{
				if (SpliceInventory.TryAddSpool(character, source.prefab, list[i], spoolStartFuel, i, out var dropped))
				{
					num += list[i];
					if (dropped)
					{
						num3++;
					}
					else
					{
						num2++;
					}
				}
			}
			if (num <= 0f)
			{
				message = "Could not create the cut rope. Inventory is full and the drop failed.";
				return false;
			}
			float num4 = Mathf.Max(0f, remainingSegmentsLive - num);
			SpliceInventory.ApplyFuelToSlot(source, num4, spoolStartFuel);
			Item val = (((Object)(object)character.data != (Object)null) ? character.data.currentItem : null);
			if ((Object)(object)val != (Object)null && (SpliceInventory.SlotMatchesHeldItem(character, source) || source == SpliceInventory.GetHeldSpoolSlot(character)))
			{
				SpliceInventory.ApplyFuelToHeldItem(val, num4, spoolStartFuel);
			}
			else
			{
				val = null;
			}
			SpliceInventory.SyncAfterChange(character, val);
			string text = ((num3 > 0 && num2 > 0) ? string.Format(CultureInfo.InvariantCulture, "{0} in inventory, {1} on the ground", num2, num3) : ((num3 <= 0) ? string.Empty : ((num3 == 1) ? "Dropped at your feet." : string.Format(CultureInfo.InvariantCulture, "Dropped {0} at your feet.", num3))));
			message = string.Format(CultureInfo.InvariantCulture, "Cut {0:0.##}m into {1} spool{2}. {3:0.##}m left.{4}{5}", RopeSpoolLengthHelper.SegmentsToMeters(num), num2 + num3, (num2 + num3 == 1) ? string.Empty : "s", RopeSpoolLengthHelper.SegmentsToMeters(num4), string.IsNullOrEmpty(text) ? string.Empty : " ", text);
			return true;
		}

		internal static List<float> PackPieces(float cutSegments, float vanillaFull)
		{
			List<float> list = new List<float>();
			if (cutSegments <= 0f)
			{
				return list;
			}
			if (vanillaFull <= 0.01f)
			{
				list.Add(cutSegments);
				return list;
			}
			float num = cutSegments;
			float num2 = 2.01f;
			while (num >= vanillaFull)
			{
				list.Add(vanillaFull);
				num -= vanillaFull;
			}
			if (num > 0.01f)
			{
				if (list.Count > 0 && num <= num2)
				{
					list[list.Count - 1] += num;
				}
				else
				{
					list.Add(num);
				}
			}
			else if (num > 0f)
			{
				if (list.Count > 0)
				{
					list[list.Count - 1] += num;
				}
				else
				{
					list.Add(cutSegments);
				}
			}
			return list;
		}

		private static string FormatPieces(List<float> pieces)
		{
			if (pieces == null || pieces.Count == 0)
			{
				return "none";
			}
			string[] array = new string[pieces.Count];
			for (int i = 0; i < pieces.Count; i++)
			{
				array[i] = RopeSpoolLengthHelper.SegmentsToMeters(pieces[i]).ToString("0.##", CultureInfo.InvariantCulture) + "m";
			}
			return string.Join(" + ", array);
		}
	}
	internal static class CutUi
	{
		internal static readonly Color PanelColor = new Color(0.08f, 0.06f, 0.05f, 0.94f);

		internal static readonly Color ButtonColor = new Color(0.179f, 0.125f, 0.09f, 0.73f);

		internal static readonly Color ButtonHover = new Color(0.26f, 0.18f, 0.13f, 0.92f);

		internal static readonly Color Cream = new Color(0.95f, 0.93f, 0.88f, 1f);

		internal static readonly Color CreamDim = new Color(0.82f, 0.78f, 0.72f, 0.92f);

		internal static readonly Color FieldWell = new Color(0.06f, 0.05f, 0.04f, 0.95f);

		internal static readonly Color Accent = new Color(0.55f, 0.42f, 0.22f, 0.95f);

		private static Sprite _whiteSprite;

		private static TMP_FontAsset _cachedFont;

		private static int _cachedSceneHandle = -1;

		internal static Canvas CreateOverlayCanvas(GameObject root, int sortingOrder)
		{
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			Canvas val = root.GetComponent<Canvas>() ?? root.AddComponent<Canvas>();
			val.renderMode = (RenderMode)0;
			val.sortingOrder = sortingOrder;
			val.additionalShaderChannels = (AdditionalCanvasShaderChannels)25;
			CanvasScaler val2 = root.GetComponent<CanvasScaler>() ?? root.AddComponent<CanvasScaler>();
			val2.uiScaleMode = (ScaleMode)1;
			val2.referenceResolution = new Vector2(1920f, 1080f);
			val2.matchWidthOrHeight = 1f;
			if ((Object)(object)root.GetComponent<GraphicRaycaster>() == (Object)null)
			{
				root.AddComponent<GraphicRaycaster>();
			}
			return val;
		}

		internal static RectTransform CreatePanel(Transform parent, string name, Vector2 size, Color color)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Expected O, but got Unknown
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: 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_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image)
			});
			val.transform.SetParent(parent, false);
			RectTransform component = val.GetComponent<RectTransform>();
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0.5f, 0.5f);
			component.anchorMax = val2;
			component.anchorMin = val2;
			component.pivot = new Vector2(0.5f, 0.5f);
			component.sizeDelta = size;
			Image component2 = val.GetComponent<Image>();
			component2.sprite = WhiteSprite();
			((Graphic)component2).color = color;
			((Graphic)component2).raycastTarget = true;
			return component;
		}

		internal static TextMeshProUGUI CreateLabel(Transform parent, string name, string text, float fontSize, FontStyles style = (FontStyles)0, TextAlignmentOptions align = (TextAlignmentOptions)513)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[2]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer)
			});
			val.transform.SetParent(parent, false);
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val2).text = text ?? string.Empty;
			((TMP_Text)val2).fontSize = fontSize;
			((TMP_Text)val2).fontStyle = style;
			((TMP_Text)val2).alignment = align;
			((Graphic)val2).color = Cream;
			((Graphic)val2).raycastTarget = false;
			((TMP_Text)val2).enableWordWrapping = true;
			((TMP_Text)val2).overflowMode = (TextOverflowModes)0;
			ApplyFont(val2);
			return val2;
		}

		internal static Button CreateButton(Transform parent, string label, UnityAction onClick, Vector2 preferredSize)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_006e: Unknown result type (might be due to invalid IL or missing references)
			//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_0091: 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_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("Btn_" + label, new Type[4]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image),
				typeof(Button)
			});
			val.transform.SetParent(parent, false);
			Image component = val.GetComponent<Image>();
			component.sprite = WhiteSprite();
			((Graphic)component).color = ButtonColor;
			Button component2 = val.GetComponent<Button>();
			((Selectable)component2).targetGraphic = (Graphic)(object)component;
			ColorBlock colors = ((Selectable)component2).colors;
			((ColorBlock)(ref colors)).normalColor = ButtonColor;
			((ColorBlock)(ref colors)).highlightedColor = ButtonHover;
			((ColorBlock)(ref colors)).pressedColor = Accent;
			((ColorBlock)(ref colors)).selectedColor = ButtonHover;
			((ColorBlock)(ref colors)).disabledColor = new Color(0.2f, 0.16f, 0.12f, 0.5f);
			((Selectable)component2).colors = colors;
			TextMeshProUGUI val2 = CreateLabel(val.transform, "Text", label, 18f, (FontStyles)0, (TextAlignmentOptions)514);
			Stretch(((TMP_Text)val2).rectTransform, 10f, 4f);
			((Graphic)val2).raycastTarget = false;
			((TMP_Text)val2).enableAutoSizing = true;
			((TMP_Text)val2).fontSizeMin = 14f;
			((TMP_Text)val2).fontSizeMax = 20f;
			LayoutElement val3 = val.AddComponent<LayoutElement>();
			val3.minHeight = preferredSize.y;
			val3.preferredHeight = preferredSize.y;
			val3.flexibleWidth = 1f;
			if (onClick != null)
			{
				((UnityEvent)component2.onClick).AddListener(onClick);
			}
			return component2;
		}

		internal static TMP_InputField CreateNumberField(Transform parent, string placeholder, string initial, UnityAction<string> onChanged)
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_0068: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: Expected O, but got Unknown
			//IL_0175: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e0: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject("NumberField", new Type[3]
			{
				typeof(RectTransform),
				typeof(CanvasRenderer),
				typeof(Image)
			});
			val.transform.SetParent(parent, false);
			Image component = val.GetComponent<Image>();
			component.sprite = WhiteSprite();
			((Graphic)component).color = FieldWell;
			LayoutElement val2 = val.AddComponent<LayoutElement>();
			val2.minHeight = 36f;
			val2.preferredHeight = 36f;
			val2.flexibleWidth = 1f;
			GameObject val3 = new GameObject("Text Area", new Type[2]
			{
				typeof(RectTransform),
				typeof(RectMask2D)
			});
			val3.transform.SetParent(val.transform, false);
			Stretch(val3.GetComponent<RectTransform>(), 10f, 6f);
			TextMeshProUGUI val4 = CreateLabel(val3.transform, "Text", initial ?? string.Empty, 18f, (FontStyles)0, (TextAlignmentOptions)513);
			Stretch(((TMP_Text)val4).rectTransform, 0f, 0f);
			((Graphic)val4).raycastTarget = true;
			TextMeshProUGUI val5 = CreateLabel(val3.transform, "Placeholder", placeholder, 18f, (FontStyles)2, (TextAlignmentOptions)513);
			Stretch(((TMP_Text)val5).rectTransform, 0f, 0f);
			((Graphic)val5).color = CreamDim;
			TMP_InputField val6 = val.AddComponent<TMP_InputField>();
			val6.textViewport = val3.GetComponent<RectTransform>();
			val6.textComponent = (TMP_Text)(object)val4;
			val6.placeholder = (Graphic)(object)val5;
			val6.contentType = (ContentType)3;
			val6.caretColor = Cream;
			val6.customCaretColor = true;
			val6.selectionColor = new Color(0.45f, 0.35f, 0.2f, 0.45f);
			val6.onFocusSelectAll = false;
			val6.restoreOriginalTextOnEscape = false;
			val6.text = initial ?? string.Empty;
			((UnityEvent<string>)(object)val6.onValueChanged).AddListener((UnityAction<string>)delegate(string s)
			{
				onChanged?.Invoke(s);
			});
			if ((Object)(object)((TMP_Text)val4).font != (Object)null)
			{
				val6.fontAsset = ((TMP_Text)val4).font;
			}
			return val6;
		}

		internal static VerticalLayoutGroup AddVertical(GameObject go, float spacing, RectOffset padding)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			VerticalLayoutGroup val = go.GetComponent<VerticalLayoutGroup>() ?? go.AddComponent<VerticalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)val).spacing = spacing;
			((LayoutGroup)val).padding = (RectOffset)(((object)padding) ?? ((object)new RectOffset()));
			((LayoutGroup)val).childAlignment = (TextAnchor)1;
			((HorizontalOrVerticalLayoutGroup)val).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)val).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)val).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)val).childForceExpandWidth = true;
			return val;
		}

		internal static HorizontalLayoutGroup AddHorizontal(GameObject go, float spacing)
		{
			HorizontalLayoutGroup val = go.GetComponent<HorizontalLayoutGroup>() ?? go.AddComponent<HorizontalLayoutGroup>();
			((HorizontalOrVerticalLayoutGroup)val).spacing = spacing;
			((LayoutGroup)val).childAlignment = (TextAnchor)3;
			((HorizontalOrVerticalLayoutGroup)val).childControlHeight = true;
			((HorizontalOrVerticalLayoutGroup)val).childControlWidth = true;
			((HorizontalOrVerticalLayoutGroup)val).childForceExpandHeight = false;
			((HorizontalOrVerticalLayoutGroup)val).childForceExpandWidth = true;
			return val;
		}

		internal static void Stretch(RectTransform rt, float padX, float padY)
		{
			//IL_0002: 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_001c: 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)
			rt.anchorMin = Vector2.zero;
			rt.anchorMax = Vector2.one;
			rt.offsetMin = new Vector2(padX, padY);
			rt.offsetMax = new Vector2(0f - padX, 0f - padY);
		}

		internal static void ApplyFont(TextMeshProUGUI text)
		{
			if (!((Object)(object)text == (Object)null))
			{
				TMP_FontAsset gameFont = GetGameFont();
				if ((Object)(object)gameFont != (Object)null)
				{
					((TMP_Text)text).font = gameFont;
				}
			}
		}

		internal static void ReleaseUiFocus()
		{
			try
			{
				EventSystem current = EventSystem.current;
				if ((Object)(object)current != (Object)null)
				{
					current.SetSelectedGameObject((GameObject)null);
				}
			}
			catch
			{
			}
		}

		internal static void EnsureEventSystem()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			if (!((Object)(object)EventSystem.current != (Object)null))
			{
				GameObject val = new GameObject("SpliceSpools_EventSystem", new Type[2]
				{
					typeof(EventSystem),
					typeof(StandaloneInputModule)
				});
				Object.DontDestroyOnLoad((Object)(object)val);
			}
		}

		private static Sprite WhiteSprite()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)_whiteSprite != (Object)null)
			{
				return _whiteSprite;
			}
			Texture2D whiteTexture = Texture2D.whiteTexture;
			_whiteSprite = Sprite.Create(whiteTexture, new Rect(0f, 0f, (float)((Texture)whiteTexture).width, (float)((Texture)whiteTexture).height), new Vector2(0.5f, 0.5f), 100f);
			return _whiteSprite;
		}

		private static TMP_FontAsset GetGameFont()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Unknown result type (might be due to invalid IL or missing references)
			Scene activeScene = SceneManager.GetActiveScene();
			int num = SceneHandle.op_Implicit(((Scene)(ref activeScene)).handle);
			if ((Object)(object)_cachedFont != (Object)null && _cachedSceneHandle == num)
			{
				return _cachedFont;
			}
			try
			{
				if ((Object)(object)GUIManager.instance != (Object)null)
				{
					TextMeshProUGUI componentInChildren = ((Component)GUIManager.instance).GetComponentInChildren<TextMeshProUGUI>(true);
					if ((Object)(object)componentInChildren != (Object)null && (Object)(object)((TMP_Text)componentInChildren).font != (Object)null)
					{
						_cachedFont = ((TMP_Text)componentInChildren).font;
						_cachedSceneHandle = num;
						return _cachedFont;
					}
				}
			}
			catch
			{
			}
			return _cachedFont;
		}
	}
	[HarmonyPatch(typeof(Item), "GetItemName")]
	internal static class Item_GetItemName_Splice_Patch
	{
		private static void Postfix(Item __instance, ItemInstanceData data, ref string __result)
		{
			try
			{
				if (SpliceInventory.IsRopeSpoolPrefab(__instance) && !string.IsNullOrEmpty(__result))
				{
					ItemInstanceData data2 = data ?? __instance.data;
					float remainingMeters = RopeSpoolLengthHelper.GetRemainingMeters(__instance, data2);
					__result = $"{__result} ({remainingMeters:0.#}m)";
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = SpliceSpoolsPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Name patch skipped: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(CharacterAfflictions), "UpdateWeight")]
	internal static class CharacterAfflictions_UpdateWeight_Splice_Patch
	{
		private static void Postfix(CharacterAfflictions __instance)
		{
			try
			{
				Character character = __instance.character;
				if (!((Object)(object)((character != null) ? character.player : null) == (Object)null))
				{
					int vanilla = 0;
					int scaled = 0;
					AddSpoolWeights(character.player.itemSlots, ref vanilla, ref scaled);
					AddSpoolWeights(GetBackpackInnerSlots(character), ref vanilla, ref scaled);
					AddSpoolWeight(character.player.GetItemSlot((byte)250), ref vanilla, ref scaled);
					if (vanilla != scaled)
					{
						float currentStatus = __instance.GetCurrentStatus((STATUSTYPE)7);
						int num = Mathf.RoundToInt(currentStatus / 0.025f);
						__instance.SetStatus((STATUSTYPE)7, 0.025f * (float)(num - vanilla + scaled), true);
					}
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = SpliceSpoolsPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Weight patch skipped: " + ex.Message));
				}
			}
		}

		private static void AddSpoolWeights(ItemSlot[] slots, ref int vanilla, ref int scaled)
		{
			if (slots != null)
			{
				foreach (ItemSlot slot in slots)
				{
					AddSpoolWeight(slot, ref vanilla, ref scaled);
				}
			}
		}

		private static void AddSpoolWeight(ItemSlot slot, ref int vanilla, ref int scaled)
		{
			if (slot != null && !slot.IsEmpty() && SpliceInventory.IsRopeSpoolPrefab(slot.prefab))
			{
				vanilla += slot.prefab.CarryWeight;
				scaled += RopeSpoolLengthHelper.GetScaledCarryWeight(slot.prefab, slot.data);
			}
		}

		private static ItemSlot[] GetBackpackInnerSlots(Character character)
		{
			if (character.player.backpackSlot == null || ((ItemSlot)character.player.backpackSlot).IsEmpty())
			{
				return null;
			}
			ItemInstanceData data = ((ItemSlot)character.player.backpackSlot).data;
			BackpackData val = default(BackpackData);
			if (data == null || !data.TryGetDataEntry<BackpackData>((DataEntryKey)7, ref val))
			{
				return null;
			}
			return val.itemSlots;
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class RopeSpool_RopeFuel_Splice_Patch
	{
		private static void Postfix(RopeSpool __instance, float value)
		{
			try
			{
				Item item = ((ItemComponent)__instance).item;
				Character val = (((Object)(object)item != (Object)null) ? item.holderCharacter : null);
				if (!((Object)(object)val == (Object)null) && val.IsLocal)
				{
					ItemSlot val2 = SpliceInventory.FindSlotForItem(val, item);
					if (val2 != null && val2.data != item.data)
					{
						SpliceInventory.ApplyFuelToSlot(val2, value, RopeSpoolLengthHelper.GetSpoolStartFuel(val2));
					}
					SpliceSpoolsPlugin.RequestWeightRefresh();
				}
			}
			catch (Exception ex)
			{
				ManualLogSource log = SpliceSpoolsPlugin.Log;
				if (log != null)
				{
					log.LogWarning((object)("Fuel sync skipped: " + ex.Message));
				}
			}
		}
	}
	[HarmonyPatch(typeof(GUIManager), "UpdateWindowStatus")]
	internal static class GUIManager_UpdateWindowStatus_CutMenu_Patch
	{
		private static void Postfix(GUIManager __instance)
		{
			if ((Object)(object)__instance != (Object)null && CutMenu.IsOpen)
			{
				CutMenu.ApplyMenuInputBlock(block: true);
			}
		}
	}
	[HarmonyPatch(typeof(Character), "CanDoInput")]
	internal static class Character_CanDoInput_CutMenu_Patch
	{
		private static void Postfix(ref bool __result)
		{
			if (CutMenu.IsOpen)
			{
				__result = false;
			}
		}
	}
	[HarmonyPatch(typeof(CursorHandler), "Update")]
	internal static class CursorHandler_Update_CutMenu_Patch
	{
		private static void Postfix()
		{
			if (CutMenu.IsOpen)
			{
				Cursor.lockState = (CursorLockMode)0;
				Cursor.visible = true;
			}
		}
	}
}