Decompiled source of Workshop Auto Sort v1.0.0

plugins/CraftingChestSorter.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
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;

[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("CraftingChestSorter")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("CraftingChestSorter")]
[assembly: AssemblyTitle("CraftingChestSorter")]
[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 BenjSammy.CraftingChestSorter
{
	[BepInPlugin("benjsammy.craftingchestsorter", "Workshop Auto Sort by BenjSammy", "1.0.0")]
	public sealed class CraftingChestSorter : BaseUnityPlugin
	{
		private sealed class OpenRequest
		{
			public bool? Granted;

			public bool Manual;
		}

		private const string Id = "benjsammy.craftingchestsorter";

		private const string SaveKey = "benjsammy.craftingchestsorter.protected";

		private static readonly ConditionalWeakTable<Player, Inventory> Protected = new ConditionalWeakTable<Player, Inventory>();

		private static readonly FieldRef<Player, Dictionary<string, string>> CustomData = AccessTools.FieldRefAccess<Player, Dictionary<string, string>>("m_customData");

		private static readonly FieldRef<Container, ZNetView> View = AccessTools.FieldRefAccess<Container, ZNetView>("m_nview");

		private static readonly string[] ChestPrefabs = new string[4] { "piece_chest_wood", "piece_chest", "piece_chest_blackmetal", "piece_chest_private" };

		private static ConfigEntry<float> radius;

		private static HashSet<string> foodPrefabs;

		private static MethodInfo checkAccess;

		private static ManualLogSource log;

		private static CraftingChestSorter instance;

		private static readonly Dictionary<Container, OpenRequest> Requests = new Dictionary<Container, OpenRequest>();

		private static MethodInfo reloadChest;

		private Coroutine sorting;

		private int sortGeneration;

		private Harmony harmony;

		private void Awake()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_00d3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Expected O, but got Unknown
			radius = ((BaseUnityPlugin)this).Config.Bind<float>("Transfer", "Chest radius metres", 60f, new ConfigDescription("Maximum distance from the crafting station in metres.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 60f), Array.Empty<object>()));
			log = ((BaseUnityPlugin)this).Logger;
			checkAccess = AccessTools.Method(typeof(Container), "CheckAccess", new Type[1] { typeof(long) }, (Type[])null);
			using (StreamReader streamReader = new StreamReader(typeof(CraftingChestSorter).Assembly.GetManifestResourceStream("ExcludedFood.txt")))
			{
				foodPrefabs = new HashSet<string>(StringComparer.Ordinal);
				string item;
				while ((item = streamReader.ReadLine()) != null)
				{
					foodPrefabs.Add(item);
				}
			}
			instance = this;
			harmony = new Harmony("benjsammy.craftingchestsorter");
			reloadChest = AccessTools.Method(typeof(Container), "Load", Type.EmptyTypes, (Type[])null);
			MethodInfo methodInfo = AccessTools.Method(typeof(Container), "RPC_OpenRespons", new Type[2]
			{
				typeof(long),
				typeof(bool)
			}, (Type[])null) ?? AccessTools.Method(typeof(Container), "RPC_OpenResponse", new Type[2]
			{
				typeof(long),
				typeof(bool)
			}, (Type[])null);
			if (methodInfo != null && reloadChest != null)
			{
				harmony.Patch((MethodBase)methodInfo, Hook("HandleOpenResponse"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				harmony.Patch((MethodBase)AccessTools.Method(typeof(Container), "Interact", new Type[3]
				{
					typeof(Humanoid),
					typeof(bool),
					typeof(bool)
				}, (Type[])null), Hook("AllowManualOpen"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
			else
			{
				reloadChest = null;
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Multiplayer chest ownership API unavailable.");
			}
			harmony.Patch((MethodBase)AccessTools.Method(typeof(InventoryGui), "UpdateInventory", new Type[1] { typeof(Player) }, (Type[])null), (HarmonyMethod)null, Hook("RecoverLegacyItems"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(Player), "Save", new Type[1] { typeof(ZPackage) }, (Type[])null), Hook("SaveProtectedInventory"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(Player), "SetCraftingStation", new Type[1] { typeof(CraftingStation) }, (Type[])null), (HarmonyMethod)null, Hook("AfterStationOpened"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static HarmonyMethod Hook(string name)
		{
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0012: Expected O, but got Unknown
			return new HarmonyMethod(typeof(CraftingChestSorter), name, (Type[])null);
		}

		private void OnDestroy()
		{
			Harmony obj = harmony;
			if (obj != null)
			{
				obj.UnpatchSelf();
			}
			Requests.Clear();
			instance = null;
		}

		private static bool HandleOpenResponse(Container __instance, bool __1)
		{
			if (!Requests.TryGetValue(__instance, out var value))
			{
				return true;
			}
			Requests.Remove(__instance);
			value.Granted = __1;
			return value.Manual;
		}

		private static void AllowManualOpen(Container __instance, Humanoid __0, bool __1)
		{
			if (!__1 && (Object)(object)__0 == (Object)(object)Player.m_localPlayer && Requests.TryGetValue(__instance, out var value))
			{
				value.Manual = true;
			}
		}

		private static bool IsSolo()
		{
			ZNet val = ZNet.instance;
			if ((Object)(object)val != (Object)null && val.IsServer() && !val.IsDedicated())
			{
				return val.GetPeerConnections() == 0;
			}
			return false;
		}

		private static Inventory LoadProtectedInventory(Player player)
		{
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Expected O, but got Unknown
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Expected O, but got Unknown
			Inventory val = new Inventory("Protected transfer", (Sprite)null, 8, 1);
			if (CustomData.Invoke(player).TryGetValue("benjsammy.craftingchestsorter.protected", out var value))
			{
				val.Load(new ZPackage(value));
			}
			return val;
		}

		private static void SaveProtectedInventory(Player __instance)
		{
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Expected O, but got Unknown
			if (!Protected.TryGetValue(__instance, out var value))
			{
				return;
			}
			try
			{
				if (value.GetAllItems().Count == 0)
				{
					CustomData.Invoke(__instance).Remove("benjsammy.craftingchestsorter.protected");
					return;
				}
				ZPackage val = new ZPackage();
				value.Save(val);
				CustomData.Invoke(__instance)["benjsammy.craftingchestsorter.protected"] = val.GetBase64();
			}
			catch (Exception ex)
			{
				log.LogError((object)("Protected row save failed: " + ex.GetBaseException()));
				throw;
			}
		}

		private static void RecoverLegacyItems(Player __0)
		{
			if ((Object)(object)__0 == (Object)null || (Object)(object)__0 != (Object)(object)Player.m_localPlayer || !IsSolo() || !CustomData.Invoke(__0).ContainsKey("benjsammy.craftingchestsorter.protected"))
			{
				return;
			}
			Inventory inventory = ((Humanoid)__0).GetInventory();
			if (inventory == null)
			{
				return;
			}
			Inventory value = Protected.GetValue(__0, LoadProtectedInventory);
			ItemData[] array = value.GetAllItems().ToArray();
			foreach (ItemData val in array)
			{
				if (inventory.CanAddItem(val, val.m_stack))
				{
					inventory.MoveItemToThis(value, val);
				}
			}
			SaveProtectedInventory(__0);
		}

		private static void AfterStationOpened(Player __instance, CraftingStation __0)
		{
			if ((Object)(object)__instance != (Object)(object)Player.m_localPlayer || (Object)(object)instance == (Object)null)
			{
				return;
			}
			instance.sortGeneration++;
			if (instance.sorting != null)
			{
				((MonoBehaviour)instance).StopCoroutine(instance.sorting);
			}
			if ((Object)(object)__0 == (Object)null || (Object)(object)ZNet.instance == (Object)null)
			{
				return;
			}
			if (!IsSolo())
			{
				if (reloadChest != null)
				{
					instance.sorting = ((MonoBehaviour)instance).StartCoroutine(instance.SortOnline(__instance, __0, instance.sortGeneration));
				}
				return;
			}
			try
			{
				TransferToChests(__instance, __0);
			}
			catch (Exception ex)
			{
				log.LogWarning((object)("Automatic transfer stopped: " + ex.GetBaseException()));
			}
		}

		private static void TransferToChests(Player player, CraftingStation station)
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory == null)
			{
				return;
			}
			Vector3 origin = ((Component)station).transform.position;
			float rangeSquared = radius.Value * radius.Value;
			Container[] array = (from chest in Object.FindObjectsOfType<Container>().Where(delegate(Container chest)
				{
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					//IL_000c: Unknown result type (might be due to invalid IL or missing references)
					//IL_0011: Unknown result type (might be due to invalid IL or missing references)
					//IL_0016: Unknown result type (might be due to invalid IL or missing references)
					Vector3 val = ((Component)chest).transform.position - origin;
					return ((Vector3)(ref val)).sqrMagnitude <= rangeSquared;
				})
				where CanUse(chest, player) && View.Invoke(chest).IsOwner()
				select chest).OrderBy(delegate(Container chest)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val = ((Component)chest).transform.position - origin;
				return ((Vector3)(ref val)).sqrMagnitude;
			}).ToArray();
			ItemData[] array2 = inventory.GetAllItems().ToArray();
			foreach (ItemData item in array2)
			{
				if (!IsEligible(item))
				{
					continue;
				}
				Container[] array3 = array;
				for (int num2 = 0; num2 < array3.Length; num2++)
				{
					Inventory inventory2 = array3[num2].GetInventory();
					if (inventory2 != null && inventory2.GetAllItems().Any((ItemData existing) => SameItem(existing, item)) && inventory2.CanAddItem(item, item.m_stack))
					{
						inventory2.MoveItemToThis(inventory, item);
						break;
					}
				}
			}
		}

		private static bool IsEligible(ItemData item)
		{
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007b: Invalid comparison between Unknown and I4
			//IL_0083: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Invalid comparison between Unknown and I4
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0099: Invalid comparison between Unknown and I4
			//IL_00a1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Invalid comparison between Unknown and I4
			if (item.m_gridPos.x >= 0 && item.m_gridPos.x < 8 && item.m_gridPos.y >= 1 && item.m_gridPos.y <= 3 && !item.m_equipped && item.m_shared != null && item.m_shared.m_teleportable && !item.IsEquipable() && (int)item.m_shared.m_itemType != 2 && (int)item.m_shared.m_itemType != 21 && (int)item.m_shared.m_itemType != 9 && (int)item.m_shared.m_itemType != 23 && (Object)(object)item.m_dropPrefab != (Object)null)
			{
				return !foodPrefabs.Contains(((Object)item.m_dropPrefab).name.Replace("(Clone)", "").TrimEnd());
			}
			return false;
		}

		private IEnumerator SortOnline(Player player, CraftingStation station, int generation)
		{
			yield return null;
			Inventory source = ((Humanoid)player).GetInventory();
			if (source == null)
			{
				yield break;
			}
			List<ItemData> remaining = source.GetAllItems().Where(IsEligible).ToList();
			Vector3 origin = ((Component)station).transform.position;
			float rangeSquared = radius.Value * radius.Value;
			Container[] array = Object.FindObjectsOfType<Container>().Where(delegate(Container val2)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val = ((Component)val2).transform.position - origin;
				return ((Vector3)(ref val)).sqrMagnitude <= rangeSquared && CanUse(val2, player);
			}).OrderBy(delegate(Container val2)
			{
				//IL_0006: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0011: Unknown result type (might be due to invalid IL or missing references)
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				Vector3 val = ((Component)val2).transform.position - origin;
				return ((Vector3)(ref val)).sqrMagnitude;
			})
				.ToArray();
			Container[] array2 = array;
			foreach (Container chest in array2)
			{
				if (remaining.Count == 0 || !IsCurrent(player, station, generation))
				{
					break;
				}
				if ((Object)(object)chest == (Object)null || !CanUse(chest, player))
				{
					continue;
				}
				Inventory destination = chest.GetInventory();
				if (destination == null || !remaining.Any((ItemData item) => destination.GetAllItems().Any((ItemData existing) => SameItem(existing, item))))
				{
					continue;
				}
				ZNetView view = View.Invoke(chest);
				if (Requests.ContainsKey(chest))
				{
					continue;
				}
				if (!view.IsOwner())
				{
					OpenRequest request = new OpenRequest();
					Requests[chest] = request;
					view.InvokeRPC("RPC_RequestOpen", new object[1] { player.GetPlayerID() });
					float deadline = Time.realtimeSinceStartup + 2f;
					while (IsCurrent(player, station, generation) && (Object)(object)chest != (Object)null && !request.Manual && request.Granted != false && (request.Granted != true || !view.IsOwner()) && Time.realtimeSinceStartup < deadline)
					{
						yield return null;
					}
					if (!IsCurrent(player, station, generation))
					{
						break;
					}
					if ((Object)(object)chest == (Object)null || request.Manual || request.Granted != true || !view.IsOwner())
					{
						continue;
					}
				}
				try
				{
					if (CanUse(chest, player) && view.IsOwner())
					{
						reloadChest.Invoke(chest, null);
						Deposit(player, source, chest, remaining);
					}
				}
				catch (Exception ex)
				{
					log.LogWarning((object)("Automatic transfer stopped: " + ex.GetBaseException()));
					break;
				}
			}
		}

		private bool IsCurrent(Player player, CraftingStation station, int generation)
		{
			if (generation == sortGeneration && (Object)(object)player != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer && (Object)(object)station != (Object)null && (Object)(object)player.GetCurrentCraftingStation() == (Object)(object)station)
			{
				return InventoryGui.IsVisible();
			}
			return false;
		}

		private static void Deposit(Player player, Inventory source, Container chest, List<ItemData> remaining)
		{
			Inventory inventory = chest.GetInventory();
			if (inventory == null)
			{
				return;
			}
			ItemData[] array = remaining.ToArray();
			foreach (ItemData item in array)
			{
				if (!source.GetAllItems().Contains(item) || !IsEligible(item))
				{
					remaining.Remove(item);
					continue;
				}
				if (!View.Invoke(chest).IsOwner() || !CanUse(chest, player))
				{
					break;
				}
				if (inventory.GetAllItems().Any((ItemData existing) => SameItem(existing, item)) && inventory.CanAddItem(item, item.m_stack))
				{
					inventory.MoveItemToThis(source, item);
					remaining.Remove(item);
				}
			}
		}

		private static bool SameItem(ItemData existing, ItemData item)
		{
			if ((Object)(object)existing.m_dropPrefab != (Object)null && (Object)(object)item.m_dropPrefab != (Object)null && ((Object)existing.m_dropPrefab).name == ((Object)item.m_dropPrefab).name && existing.m_quality == item.m_quality)
			{
				return existing.m_variant == item.m_variant;
			}
			return false;
		}

		private static bool CanUse(Container chest, Player player)
		{
			//IL_0091: Unknown result type (might be due to invalid IL or missing references)
			string value = ((Object)((Component)chest).gameObject).name.Replace("(Clone)", "").TrimEnd();
			ZNetView val = View.Invoke(chest);
			if (Array.IndexOf(ChestPrefabs, value) >= 0 && (Object)(object)val != (Object)null && val.IsValid() && !chest.IsInUse())
			{
				object obj = checkAccess?.Invoke(chest, new object[1] { player.GetPlayerID() });
				if (obj is bool && (bool)obj)
				{
					return PrivateArea.CheckAccess(((Component)chest).transform.position, 0f, false, false);
				}
			}
			return false;
		}
	}
}